mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-10-17 06:36:18 +00:00
Compatibility fixes with thread-safe list tracker changes.
This commit is contained in:
@@ -168,6 +168,7 @@ libsub_root_a_SOURCES = \
|
|||||||
utils/directory.h \
|
utils/directory.h \
|
||||||
utils/file_status_cache.cc \
|
utils/file_status_cache.cc \
|
||||||
utils/file_status_cache.h \
|
utils/file_status_cache.h \
|
||||||
|
utils/functional.h \
|
||||||
utils/list_focus.h \
|
utils/list_focus.h \
|
||||||
utils/lockfile.cc \
|
utils/lockfile.cc \
|
||||||
utils/lockfile.h \
|
utils/lockfile.h \
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ apply_d_change_link(core::Download* download, const torrent::Object::list_type&
|
|||||||
const std::string& type = (itr++)->as_string();
|
const std::string& type = (itr++)->as_string();
|
||||||
const std::string& prefix = (itr++)->as_string();
|
const std::string& prefix = (itr++)->as_string();
|
||||||
const std::string& postfix = (itr++)->as_string();
|
const std::string& postfix = (itr++)->as_string();
|
||||||
|
|
||||||
if (type.empty())
|
if (type.empty())
|
||||||
throw torrent::input_error("Invalid arguments.");
|
throw torrent::input_error("Invalid arguments.");
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ t_multicall(core::Download* download, const torrent::Object::list_type& args) {
|
|||||||
const std::string& cmd = cItr->as_string();
|
const std::string& cmd = cItr->as_string();
|
||||||
auto t = download->tracker_list()->at(itr);
|
auto t = download->tracker_list()->at(itr);
|
||||||
|
|
||||||
row.push_back(rpc::parse_command(rpc::make_target(t), cmd.c_str(), cmd.c_str() + cmd.size()).first);
|
row.push_back(rpc::parse_command(rpc::make_target(&t), cmd.c_str(), cmd.c_str() + cmd.size()).first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-31
@@ -58,40 +58,33 @@ apply_tos(const torrent::Object::string_type& arg) {
|
|||||||
|
|
||||||
torrent::Object apply_encoding_list(const std::string& arg) { torrent::encoding_list()->push_back(arg); return torrent::Object(); }
|
torrent::Object apply_encoding_list(const std::string& arg) { torrent::encoding_list()->push_back(arg); return torrent::Object(); }
|
||||||
|
|
||||||
torrent::File*
|
|
||||||
rpc_find_file(core::Download* download, uint32_t index) {
|
|
||||||
if (index >= download->file_list()->size_files())
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return (*download->file_list())[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ergh... time to update the Tracker API to allow proper ptrs.
|
|
||||||
torrent::tracker::Tracker*
|
|
||||||
rpc_find_tracker(core::Download* download, uint32_t index) {
|
|
||||||
if (index >= download->tracker_list()->size())
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return download->tracker_list()->at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
torrent::Peer*
|
|
||||||
rpc_find_peer(core::Download* download, const torrent::HashString& hash) {
|
|
||||||
torrent::ConnectionList::iterator itr = download->connection_list()->find(hash.c_str());
|
|
||||||
|
|
||||||
if (itr == download->connection_list()->end())
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return *itr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
initialize_rpc() {
|
initialize_rpc() {
|
||||||
rpc::rpc.initialize();
|
rpc::rpc.initialize();
|
||||||
rpc::rpc.slot_find_download() = [](const char* hash) { return control->core()->download_list()->find_hex_ptr(hash); };
|
|
||||||
rpc::rpc.slot_find_file() = [](core::Download* d, uint32_t index) { return rpc_find_file(d, index); };
|
rpc::rpc.slot_find_download() = [](const char* hash) {
|
||||||
rpc::rpc.slot_find_tracker() = [](core::Download* d, uint32_t index) { return rpc_find_tracker(d, index); };
|
return control->core()->download_list()->find_hex_ptr(hash);
|
||||||
rpc::rpc.slot_find_peer() = [](core::Download* d, const torrent::HashString& hash) { return rpc_find_peer(d, hash); };
|
};
|
||||||
|
rpc::rpc.slot_find_file() = [](core::Download* d, uint32_t index) -> torrent::File* {
|
||||||
|
if (index >= d->file_list()->size_files())
|
||||||
|
throw torrent::input_error("invalid parameters: index not found");
|
||||||
|
|
||||||
|
return (*d->file_list())[index];
|
||||||
|
};
|
||||||
|
rpc::rpc.slot_find_tracker() = [](core::Download* d, uint32_t index) -> torrent::tracker::Tracker {
|
||||||
|
if (index >= d->tracker_list()->size())
|
||||||
|
throw torrent::input_error("invalid parameters: index not found");
|
||||||
|
|
||||||
|
return d->tracker_list()->at(index);
|
||||||
|
};
|
||||||
|
rpc::rpc.slot_find_peer() = [](core::Download* d, const torrent::HashString& hash) -> torrent::Peer* {
|
||||||
|
auto itr = d->connection_list()->find(hash.c_str());
|
||||||
|
|
||||||
|
if (itr == d->connection_list()->end())
|
||||||
|
throw torrent::input_error("invalid parameters: hash not found");
|
||||||
|
|
||||||
|
return *itr;
|
||||||
|
};
|
||||||
|
|
||||||
unsigned int count = 0;
|
unsigned int count = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <rak/address_info.h>
|
#include <rak/address_info.h>
|
||||||
#include <rak/error_number.h>
|
#include <rak/error_number.h>
|
||||||
#include <torrent/dht_manager.h>
|
#include <torrent/tracker/dht_controller.h>
|
||||||
#include <torrent/tracker/tracker.h>
|
#include <torrent/tracker/tracker.h>
|
||||||
#include <torrent/utils/log.h>
|
#include <torrent/utils/log.h>
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ struct call_add_node_t {
|
|||||||
if (sa == NULL) {
|
if (sa == NULL) {
|
||||||
lt_log_print(torrent::LOG_DHT_WARN, "Could not resolve host.");
|
lt_log_print(torrent::LOG_DHT_WARN, "Could not resolve host.");
|
||||||
} else {
|
} else {
|
||||||
torrent::dht_manager()->add_node(sa, m_port);
|
torrent::dht_controller()->add_node(sa, m_port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ struct call_add_node_t {
|
|||||||
|
|
||||||
torrent::Object
|
torrent::Object
|
||||||
apply_dht_add_node(const std::string& arg) {
|
apply_dht_add_node(const std::string& arg) {
|
||||||
if (!torrent::dht_manager()->is_valid())
|
if (!torrent::dht_controller()->is_valid())
|
||||||
throw torrent::input_error("DHT not enabled.");
|
throw torrent::input_error("DHT not enabled.");
|
||||||
|
|
||||||
int port, ret;
|
int port, ret;
|
||||||
|
|||||||
+23
-59
@@ -1,47 +1,11 @@
|
|||||||
// rTorrent - BitTorrent client
|
|
||||||
// Copyright (C) 2005-2011, Jari Sundell
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations
|
|
||||||
// including the two.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Contact: Jari Sundell <jaris@ifi.uio.no>
|
|
||||||
//
|
|
||||||
// Skomakerveien 33
|
|
||||||
// 3185 Skoppum, NORWAY
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
#include <torrent/dht_manager.h>
|
|
||||||
#include <torrent/object_stream.h>
|
#include <torrent/object_stream.h>
|
||||||
#include <torrent/rate.h>
|
#include <torrent/rate.h>
|
||||||
|
#include <torrent/tracker/dht_controller.h>
|
||||||
#include <torrent/utils/log.h>
|
#include <torrent/utils/log.h>
|
||||||
|
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
@@ -94,7 +58,7 @@ DhtManager::load_dht_cache() {
|
|||||||
LT_LOG_THIS("could not open cache file (path:%s)", cache_filename.c_str());
|
LT_LOG_THIS("could not open cache file (path:%s)", cache_filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::dht_manager()->initialize(cache);
|
torrent::dht_controller()->initialize(cache);
|
||||||
|
|
||||||
if (m_start == dht_on)
|
if (m_start == dht_on)
|
||||||
start_dht();
|
start_dht();
|
||||||
@@ -104,31 +68,31 @@ void
|
|||||||
DhtManager::start_dht() {
|
DhtManager::start_dht() {
|
||||||
priority_queue_erase(&taskScheduler, &m_stopTimeout);
|
priority_queue_erase(&taskScheduler, &m_stopTimeout);
|
||||||
|
|
||||||
if (!torrent::dht_manager()->is_valid()) {
|
if (!torrent::dht_controller()->is_valid()) {
|
||||||
LT_LOG_THIS("server start skipped, manager is uninitialized", 0);
|
LT_LOG_THIS("server start skipped, manager is uninitialized", 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (torrent::dht_manager()->is_active()) {
|
if (torrent::dht_controller()->is_active()) {
|
||||||
LT_LOG_THIS("server start skipped, already active", 0);
|
LT_LOG_THIS("server start skipped, already active", 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::ThrottlePair throttles = control->core()->get_throttle(m_throttleName);
|
torrent::ThrottlePair throttles = control->core()->get_throttle(m_throttleName);
|
||||||
torrent::dht_manager()->set_upload_throttle(throttles.first);
|
torrent::dht_controller()->set_upload_throttle(throttles.first);
|
||||||
torrent::dht_manager()->set_download_throttle(throttles.second);
|
torrent::dht_controller()->set_download_throttle(throttles.second);
|
||||||
|
|
||||||
int port = rpc::call_command_value("dht.port");
|
int port = rpc::call_command_value("dht.port");
|
||||||
|
|
||||||
if (port <= 0)
|
if (port <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!torrent::dht_manager()->start(port)) {
|
if (!torrent::dht_controller()->start(port)) {
|
||||||
m_start = dht_off;
|
m_start = dht_off;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::dht_manager()->reset_statistics();
|
torrent::dht_controller()->reset_statistics();
|
||||||
|
|
||||||
m_updateTimeout.slot() = std::bind(&DhtManager::update, this);
|
m_updateTimeout.slot() = std::bind(&DhtManager::update, this);
|
||||||
priority_queue_insert(&taskScheduler, &m_updateTimeout, (cachedTime + rak::timer::from_seconds(60)).round_seconds());
|
priority_queue_insert(&taskScheduler, &m_updateTimeout, (cachedTime + rak::timer::from_seconds(60)).round_seconds());
|
||||||
@@ -146,17 +110,17 @@ DhtManager::stop_dht() {
|
|||||||
priority_queue_erase(&taskScheduler, &m_updateTimeout);
|
priority_queue_erase(&taskScheduler, &m_updateTimeout);
|
||||||
priority_queue_erase(&taskScheduler, &m_stopTimeout);
|
priority_queue_erase(&taskScheduler, &m_stopTimeout);
|
||||||
|
|
||||||
if (torrent::dht_manager()->is_active()) {
|
if (torrent::dht_controller()->is_active()) {
|
||||||
LT_LOG_THIS("stopping server", 0);
|
LT_LOG_THIS("stopping server", 0);
|
||||||
|
|
||||||
log_statistics(true);
|
log_statistics(true);
|
||||||
torrent::dht_manager()->stop();
|
torrent::dht_controller()->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DhtManager::save_dht_cache() {
|
DhtManager::save_dht_cache() {
|
||||||
if (!control->core()->download_store()->is_enabled() || !torrent::dht_manager()->is_valid())
|
if (!control->core()->download_store()->is_enabled() || !torrent::dht_controller()->is_valid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string filename = control->core()->download_store()->path() + "rtorrent.dht_cache";
|
std::string filename = control->core()->download_store()->path() + "rtorrent.dht_cache";
|
||||||
@@ -167,7 +131,7 @@ DhtManager::save_dht_cache() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
torrent::Object cache = torrent::Object::create_map();
|
torrent::Object cache = torrent::Object::create_map();
|
||||||
cache_file << *torrent::dht_manager()->store_cache(&cache);
|
cache_file << *torrent::dht_controller()->store_cache(&cache);
|
||||||
|
|
||||||
if (!cache_file.good())
|
if (!cache_file.good())
|
||||||
return;
|
return;
|
||||||
@@ -198,7 +162,7 @@ DhtManager::set_mode(const std::string& arg) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
DhtManager::update() {
|
DhtManager::update() {
|
||||||
if (!torrent::dht_manager()->is_active())
|
if (!torrent::dht_controller()->is_active())
|
||||||
throw torrent::internal_error("DhtManager::update called with DHT inactive.");
|
throw torrent::internal_error("DhtManager::update called with DHT inactive.");
|
||||||
|
|
||||||
if (m_start == dht_auto && !m_stopTimeout.is_queued()) {
|
if (m_start == dht_auto && !m_stopTimeout.is_queued()) {
|
||||||
@@ -207,7 +171,7 @@ DhtManager::update() {
|
|||||||
for (itr = control->core()->download_list()->begin(), end = control->core()->download_list()->end(); itr != end; ++itr)
|
for (itr = control->core()->download_list()->begin(), end = control->core()->download_list()->end(); itr != end; ++itr)
|
||||||
if ((*itr)->download()->info()->is_active() && !(*itr)->download()->info()->is_private())
|
if ((*itr)->download()->info()->is_active() && !(*itr)->download()->info()->is_private())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (itr == end) {
|
if (itr == end) {
|
||||||
m_stopTimeout.slot() = std::bind(&DhtManager::stop_dht, this);
|
m_stopTimeout.slot() = std::bind(&DhtManager::stop_dht, this);
|
||||||
priority_queue_insert(&taskScheduler, &m_stopTimeout, (cachedTime + rak::timer::from_seconds(15 * 60)).round_seconds());
|
priority_queue_insert(&taskScheduler, &m_stopTimeout, (cachedTime + rak::timer::from_seconds(15 * 60)).round_seconds());
|
||||||
@@ -223,17 +187,17 @@ DhtManager::update() {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
DhtManager::log_statistics(bool force) {
|
DhtManager::log_statistics(bool force) {
|
||||||
torrent::DhtManager::statistics_type stats = torrent::dht_manager()->get_statistics();
|
auto stats = torrent::dht_controller()->get_statistics();
|
||||||
|
|
||||||
// Check for firewall problems.
|
// Check for firewall problems.
|
||||||
|
|
||||||
if (stats.cycle > 2 && stats.queries_sent - m_dhtPrevQueriesSent > 100 && stats.queries_received == m_dhtPrevQueriesReceived) {
|
if (stats.cycle > 2 && stats.queries_sent - m_dhtPrevQueriesSent > 100 && stats.queries_received == m_dhtPrevQueriesReceived) {
|
||||||
// We should have had clients ping us at least but have received
|
// We should have had clients ping us at least but have received
|
||||||
// nothing, that means the UDP port is probably unreachable.
|
// nothing, that means the UDP port is probably unreachable.
|
||||||
if (torrent::dht_manager()->can_receive_queries())
|
if (torrent::dht_controller()->is_receiving_requests())
|
||||||
LT_LOG_THIS("listening port appears to be unreachable, no queries received", 0);
|
LT_LOG_THIS("listening port appears to be unreachable, no queries received", 0);
|
||||||
|
|
||||||
torrent::dht_manager()->set_can_receive(false);
|
torrent::dht_controller()->set_receive_requests(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stats.queries_sent - m_dhtPrevQueriesSent > stats.num_nodes * 2 + 20 && stats.replies_received == m_dhtPrevRepliesReceived) {
|
if (stats.queries_sent - m_dhtPrevQueriesSent > stats.num_nodes * 2 + 20 && stats.replies_received == m_dhtPrevRepliesReceived) {
|
||||||
@@ -248,7 +212,7 @@ DhtManager::log_statistics(bool force) {
|
|||||||
m_warned = false;
|
m_warned = false;
|
||||||
|
|
||||||
if (stats.queries_received > m_dhtPrevQueriesReceived)
|
if (stats.queries_received > m_dhtPrevQueriesReceived)
|
||||||
torrent::dht_manager()->set_can_receive(true);
|
torrent::dht_controller()->set_receive_requests(true);
|
||||||
|
|
||||||
// Nothing to log while bootstrapping, but check again every minute.
|
// Nothing to log while bootstrapping, but check again every minute.
|
||||||
if (stats.cycle <= 1) {
|
if (stats.cycle <= 1) {
|
||||||
@@ -269,7 +233,7 @@ DhtManager::log_statistics(bool force) {
|
|||||||
// afterwards (i.e. every 2 hours), or when forced.
|
// afterwards (i.e. every 2 hours), or when forced.
|
||||||
if ((force && stats.cycle != m_dhtPrevCycle) || stats.cycle == 3 || stats.cycle > m_dhtPrevCycle + 7) {
|
if ((force && stats.cycle != m_dhtPrevCycle) || stats.cycle == 3 || stats.cycle > m_dhtPrevCycle + 7) {
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
snprintf(buffer, sizeof(buffer),
|
snprintf(buffer, sizeof(buffer),
|
||||||
"DHT statistics: %d queries in, %d queries out, %d replies received, %lld bytes read, %lld bytes sent, "
|
"DHT statistics: %d queries in, %d queries out, %d replies received, %lld bytes read, %lld bytes sent, "
|
||||||
"%d known nodes in %d buckets, %d peers (highest: %d) tracked in %d torrents.",
|
"%d known nodes in %d buckets, %d peers (highest: %d) tracked in %d torrents.",
|
||||||
stats.queries_received - m_dhtPrevQueriesReceived,
|
stats.queries_received - m_dhtPrevQueriesReceived,
|
||||||
@@ -301,11 +265,11 @@ DhtManager::dht_statistics() {
|
|||||||
torrent::Object dhtStats = torrent::Object::create_map();
|
torrent::Object dhtStats = torrent::Object::create_map();
|
||||||
|
|
||||||
dhtStats.insert_key("dht", dht_settings[m_start]);
|
dhtStats.insert_key("dht", dht_settings[m_start]);
|
||||||
dhtStats.insert_key("active", torrent::dht_manager()->is_active());
|
dhtStats.insert_key("active", torrent::dht_controller()->is_active());
|
||||||
dhtStats.insert_key("throttle", m_throttleName);
|
dhtStats.insert_key("throttle", m_throttleName);
|
||||||
|
|
||||||
if (torrent::dht_manager()->is_active()) {
|
if (torrent::dht_controller()->is_active()) {
|
||||||
torrent::DhtManager::statistics_type stats = torrent::dht_manager()->get_statistics();
|
auto stats = torrent::dht_controller()->get_statistics();
|
||||||
|
|
||||||
dhtStats.insert_key("cycle", stats.cycle);
|
dhtStats.insert_key("cycle", stats.cycle);
|
||||||
dhtStats.insert_key("queries_received", stats.queries_received);
|
dhtStats.insert_key("queries_received", stats.queries_received);
|
||||||
@@ -327,7 +291,7 @@ DhtManager::dht_statistics() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
DhtManager::set_throttle_name(const std::string& throttleName) {
|
DhtManager::set_throttle_name(const std::string& throttleName) {
|
||||||
if (torrent::dht_manager()->is_active())
|
if (torrent::dht_controller()->is_active())
|
||||||
throw torrent::input_error("Cannot set DHT throttle while active.");
|
throw torrent::input_error("Cannot set DHT throttle while active.");
|
||||||
|
|
||||||
m_throttleName = throttleName;
|
m_throttleName = throttleName;
|
||||||
|
|||||||
@@ -1,39 +1,3 @@
|
|||||||
// rTorrent - BitTorrent client
|
|
||||||
// Copyright (C) 2005-2011, Jari Sundell
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations
|
|
||||||
// including the two.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Contact: Jari Sundell <jaris@ifi.uio.no>
|
|
||||||
//
|
|
||||||
// Skomakerveien 33
|
|
||||||
// 3185 Skoppum, NORWAY
|
|
||||||
|
|
||||||
#ifndef RTORRENT_CORE_DHT_MANAGER_H
|
#ifndef RTORRENT_CORE_DHT_MANAGER_H
|
||||||
#define RTORRENT_CORE_DHT_MANAGER_H
|
#define RTORRENT_CORE_DHT_MANAGER_H
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ Download::~Download() {
|
|||||||
void
|
void
|
||||||
Download::enable_udp_trackers(bool state) {
|
Download::enable_udp_trackers(bool state) {
|
||||||
for (torrent::TrackerList::iterator itr = m_download.tracker_list()->begin(), last = m_download.tracker_list()->end(); itr != last; ++itr)
|
for (torrent::TrackerList::iterator itr = m_download.tracker_list()->begin(), last = m_download.tracker_list()->end(); itr != last; ++itr)
|
||||||
if ((*itr)->type() == torrent::TRACKER_UDP) {
|
if (itr->type() == torrent::TRACKER_UDP) {
|
||||||
if (state)
|
if (state)
|
||||||
(*itr)->enable();
|
itr->enable();
|
||||||
else
|
else
|
||||||
(*itr)->disable();
|
itr->disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,10 +164,10 @@ print_download_status(char* first, char* last, core::Download* d) {
|
|||||||
torrent::TrackerList::iterator itr =
|
torrent::TrackerList::iterator itr =
|
||||||
std::find_if(d->tracker_list()->begin(), d->tracker_list()->end(),
|
std::find_if(d->tracker_list()->begin(), d->tracker_list()->end(),
|
||||||
std::mem_fn(&torrent::tracker::Tracker::is_busy_not_scrape));
|
std::mem_fn(&torrent::tracker::Tracker::is_busy_not_scrape));
|
||||||
auto status = (*itr)->status();
|
auto status = itr->status();
|
||||||
|
|
||||||
first = print_buffer(first, last, "Tracker[%i:%i]: Connecting to %s %s",
|
first = print_buffer(first, last, "Tracker[%i:%i]: Connecting to %s %s",
|
||||||
(*itr)->group(), std::distance(d->tracker_list()->begin(), itr), (*itr)->url().c_str(), status.c_str());
|
itr->group(), std::distance(d->tracker_list()->begin(), itr), itr->url().c_str(), status.c_str());
|
||||||
|
|
||||||
} else if (!d->message().empty()) {
|
} else if (!d->message().empty()) {
|
||||||
first = print_buffer(first, last, "%s", d->message().c_str());
|
first = print_buffer(first, last, "%s", d->message().c_str());
|
||||||
|
|||||||
@@ -42,36 +42,36 @@ WindowTrackerList::redraw() {
|
|||||||
typedef std::pair<unsigned int, unsigned int> Range;
|
typedef std::pair<unsigned int, unsigned int> Range;
|
||||||
|
|
||||||
Range range = rak::advance_bidirectional<unsigned int>(0, *m_focus, tl->size(), (m_canvas->height() - 1) / 2);
|
Range range = rak::advance_bidirectional<unsigned int>(0, *m_focus, tl->size(), (m_canvas->height() - 1) / 2);
|
||||||
unsigned int group = tl->at(range.first)->group();
|
unsigned int group = tl->at(range.first).group();
|
||||||
|
|
||||||
while (range.first != range.second) {
|
while (range.first != range.second) {
|
||||||
auto tracker = tl->at(range.first);
|
auto tracker = tl->at(range.first);
|
||||||
|
|
||||||
if (tracker->group() == group)
|
if (tracker.group() == group)
|
||||||
m_canvas->print(0, pos, "%2i:", group++);
|
m_canvas->print(0, pos, "%2i:", group++);
|
||||||
|
|
||||||
m_canvas->print(4, pos++, "%s",
|
m_canvas->print(4, pos++, "%s",
|
||||||
tracker->url().c_str());
|
tracker.url().c_str());
|
||||||
|
|
||||||
if (pos < m_canvas->height()) {
|
if (pos < m_canvas->height()) {
|
||||||
const char* state;
|
const char* state;
|
||||||
|
|
||||||
if (tracker->is_busy_not_scrape())
|
if (tracker.is_busy_not_scrape())
|
||||||
state = "req ";
|
state = "req ";
|
||||||
else if (tracker->is_busy())
|
else if (tracker.is_busy())
|
||||||
state = "scr ";
|
state = "scr ";
|
||||||
else
|
else
|
||||||
state = " ";
|
state = " ";
|
||||||
|
|
||||||
auto tracker_state = tracker->state();
|
auto tracker_state = tracker.state();
|
||||||
|
|
||||||
m_canvas->print(0, pos++, "%s Id: %s Counters: %uf / %us (%u) %s S/L/D: %u/%u/%u (%u/%u)",
|
m_canvas->print(0, pos++, "%s Id: %s Counters: %uf / %us (%u) %s S/L/D: %u/%u/%u (%u/%u)",
|
||||||
state,
|
state,
|
||||||
rak::copy_escape_html(tracker->tracker_id()).c_str(),
|
rak::copy_escape_html(tracker.tracker_id()).c_str(),
|
||||||
tracker_state.failed_counter(),
|
tracker_state.failed_counter(),
|
||||||
tracker_state.success_counter(),
|
tracker_state.success_counter(),
|
||||||
tracker_state.scrape_counter(),
|
tracker_state.scrape_counter(),
|
||||||
tracker->is_usable() ? " on" : tracker->is_enabled() ? "err" : "off",
|
tracker.is_usable() ? " on" : tracker.is_enabled() ? "err" : "off",
|
||||||
tracker_state.scrape_complete(),
|
tracker_state.scrape_complete(),
|
||||||
tracker_state.scrape_incomplete(),
|
tracker_state.scrape_incomplete(),
|
||||||
tracker_state.scrape_downloaded(),
|
tracker_state.scrape_downloaded(),
|
||||||
@@ -84,7 +84,7 @@ WindowTrackerList::redraw() {
|
|||||||
m_canvas->set_attr(4, pos - 1, m_canvas->width(), is_focused() ? A_REVERSE : A_BOLD, COLOR_PAIR(0));
|
m_canvas->set_attr(4, pos - 1, m_canvas->width(), is_focused() ? A_REVERSE : A_BOLD, COLOR_PAIR(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tracker->is_busy()) {
|
if (tracker.is_busy()) {
|
||||||
m_canvas->set_attr(0, pos - 2, 4, A_REVERSE, COLOR_PAIR(0));
|
m_canvas->set_attr(0, pos - 2, 4, A_REVERSE, COLOR_PAIR(0));
|
||||||
m_canvas->set_attr(0, pos - 1, 4, A_REVERSE, COLOR_PAIR(0));
|
m_canvas->set_attr(0, pos - 1, 4, A_REVERSE, COLOR_PAIR(0));
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-8
@@ -2,21 +2,20 @@
|
|||||||
|
|
||||||
#include "rpc/jsonrpc.h"
|
#include "rpc/jsonrpc.h"
|
||||||
|
|
||||||
#include "rpc/rpc_manager.h"
|
|
||||||
#include "torrent/exceptions.h"
|
|
||||||
#include "torrent/object.h"
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <torrent/common.h>
|
#include <torrent/common.h>
|
||||||
#include <torrent/torrent.h>
|
#include <torrent/torrent.h>
|
||||||
|
|
||||||
|
#include "thread_base.h"
|
||||||
|
#include "rpc/rpc_manager.h"
|
||||||
#include "rpc/command.h"
|
#include "rpc/command.h"
|
||||||
#include "rpc/command_map.h"
|
#include "rpc/command_map.h"
|
||||||
|
#include "rpc/nlohmann/json.h"
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
#include "thread_base.h"
|
#include "torrent/exceptions.h"
|
||||||
|
#include "torrent/object.h"
|
||||||
#include "nlohmann/json.h"
|
#include "utils/functional.h"
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
@@ -123,6 +122,9 @@ jsonrpc_call_command(const std::string& method, const json& params) {
|
|||||||
auto& params_object_list = params_object.as_list();
|
auto& params_object_list = params_object.as_list();
|
||||||
rpc::target_type target = rpc::make_target();
|
rpc::target_type target = rpc::make_target();
|
||||||
|
|
||||||
|
std::function<void()> deleter = []() {};
|
||||||
|
utils::scope_guard guard([&deleter]() { deleter(); });
|
||||||
|
|
||||||
if (!(itr->second.m_flags & CommandMap::flag_no_target)) {
|
if (!(itr->second.m_flags & CommandMap::flag_no_target)) {
|
||||||
// Provide a blank target if none was provided
|
// Provide a blank target if none was provided
|
||||||
if (params_object_list.empty())
|
if (params_object_list.empty())
|
||||||
@@ -131,7 +133,8 @@ jsonrpc_call_command(const std::string& method, const json& params) {
|
|||||||
if (!params_object_list.begin()->is_string())
|
if (!params_object_list.begin()->is_string())
|
||||||
throw torrent::input_error("invalid parameters: target must be a string");
|
throw torrent::input_error("invalid parameters: target must be a string");
|
||||||
|
|
||||||
RpcManager::object_to_target(params_object_list.begin()->as_string(), itr->second.m_flags, &target);
|
RpcManager::object_to_target(params_object_list.begin()->as_string(), itr->second.m_flags, &target, &deleter);
|
||||||
|
|
||||||
params_object_list.erase(params_object_list.begin());
|
params_object_list.erase(params_object_list.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "rpc/rpc_manager.h"
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -46,13 +45,10 @@
|
|||||||
|
|
||||||
#include "parse.h"
|
#include "parse.h"
|
||||||
#include "parse_commands.h"
|
#include "parse_commands.h"
|
||||||
|
#include "rpc/rpc_manager.h"
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
CommandMap commands;
|
|
||||||
RpcManager rpc;
|
|
||||||
ExecFile execFile;
|
|
||||||
|
|
||||||
inline bool command_map_is_space(char c) {
|
inline bool command_map_is_space(char c) {
|
||||||
return c == ' ' || c == '\t';
|
return c == ' ' || c == '\t';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "command_map.h"
|
|
||||||
#include "exec_file.h"
|
|
||||||
#include "xmlrpc.h"
|
#include "xmlrpc.h"
|
||||||
#include "rpc_manager.h"
|
#include "rpc_manager.h"
|
||||||
|
|
||||||
@@ -51,11 +49,6 @@ namespace core {
|
|||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
// Move to another file?
|
|
||||||
extern CommandMap commands;
|
|
||||||
extern RpcManager rpc;
|
|
||||||
extern ExecFile execFile;
|
|
||||||
|
|
||||||
typedef std::pair<torrent::Object, const char*> parse_command_type;
|
typedef std::pair<torrent::Object, const char*> parse_command_type;
|
||||||
|
|
||||||
// The generic parse command function, used by the rest. At some point
|
// The generic parse command function, used by the rest. At some point
|
||||||
|
|||||||
+24
-10
@@ -9,8 +9,12 @@
|
|||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
|
CommandMap commands;
|
||||||
|
RpcManager rpc;
|
||||||
|
ExecFile execFile;
|
||||||
|
|
||||||
void
|
void
|
||||||
RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target) {
|
RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target, std::function<void()>* deleter) {
|
||||||
if (call_flags & CommandMap::flag_no_target)
|
if (call_flags & CommandMap::flag_no_target)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -31,6 +35,7 @@ RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::ta
|
|||||||
char type = 'd';
|
char type = 'd';
|
||||||
std::string hash;
|
std::string hash;
|
||||||
std::string index;
|
std::string index;
|
||||||
|
|
||||||
const auto& delim_pos = target_string.find_first_of(':', 40);
|
const auto& delim_pos = target_string.find_first_of(':', 40);
|
||||||
if (delim_pos == target_string.npos ||
|
if (delim_pos == target_string.npos ||
|
||||||
delim_pos + 2 >= target_string.size()) {
|
delim_pos + 2 >= target_string.size()) {
|
||||||
@@ -49,34 +54,43 @@ RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::ta
|
|||||||
throw torrent::input_error("invalid parameters: info-hash not found");
|
throw torrent::input_error("invalid parameters: info-hash not found");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
torrent::tracker::Tracker* tracker;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'd':
|
case 'd':
|
||||||
*target = rpc::make_target(download);
|
*target = rpc::make_target(download);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'f':
|
case 'f':
|
||||||
*target = rpc::make_target(
|
*target = rpc::make_target(command_base::target_file,
|
||||||
command_base::target_file,
|
rpc.slot_find_file()(download, std::stoi(std::string(index))));
|
||||||
rpc.slot_find_file()(download, std::stoi(std::string(index))));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
*target = rpc::make_target(
|
tracker = new torrent::tracker::Tracker(rpc.slot_find_tracker()(download, std::stoi(std::string(index))));
|
||||||
command_base::target_tracker,
|
|
||||||
rpc.slot_find_tracker()(download, std::stoi(std::string(index))));
|
*target = rpc::make_target(command_base::target_tracker, tracker);
|
||||||
|
*deleter = [tracker]() { delete tracker; };
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p': {
|
case 'p': {
|
||||||
if (index.size() < 40) {
|
if (index.size() < 40) {
|
||||||
throw torrent::input_error("invalid parameters: not a hash string.");
|
throw torrent::input_error("invalid parameters: not a hash string.");
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::HashString hash;
|
torrent::HashString hash;
|
||||||
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
||||||
*target = rpc::make_target(
|
|
||||||
command_base::target_peer,
|
*target = rpc::make_target(command_base::target_peer,
|
||||||
rpc.slot_find_peer()(download, hash));
|
rpc.slot_find_peer()(download, hash));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw torrent::input_error("invalid parameters: unexpected target type");
|
throw torrent::input_error("invalid parameters: unexpected target type");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const std::logic_error&) {
|
} catch (const std::logic_error&) {
|
||||||
throw torrent::input_error("invalid parameters: invalid index");
|
throw torrent::input_error("invalid parameters: invalid index");
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -6,6 +6,8 @@
|
|||||||
#include <torrent/common.h>
|
#include <torrent/common.h>
|
||||||
|
|
||||||
#include "rpc/command.h"
|
#include "rpc/command.h"
|
||||||
|
#include "rpc/command_map.h"
|
||||||
|
#include "rpc/exec_file.h"
|
||||||
#include "rpc/jsonrpc.h"
|
#include "rpc/jsonrpc.h"
|
||||||
#include "rpc/xmlrpc.h"
|
#include "rpc/xmlrpc.h"
|
||||||
|
|
||||||
@@ -15,6 +17,9 @@ class Download;
|
|||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
|
extern ExecFile execFile;
|
||||||
|
extern CommandMap commands;
|
||||||
|
|
||||||
class rpc_error : public torrent::base_error {
|
class rpc_error : public torrent::base_error {
|
||||||
public:
|
public:
|
||||||
rpc_error(int type, std::string msg) :
|
rpc_error(int type, std::string msg) :
|
||||||
@@ -33,7 +38,7 @@ class RpcManager {
|
|||||||
public:
|
public:
|
||||||
using slot_download = std::function<core::Download*(const char*)>;
|
using slot_download = std::function<core::Download*(const char*)>;
|
||||||
using slot_file = std::function<torrent::File*(core::Download*, uint32_t)>;
|
using slot_file = std::function<torrent::File*(core::Download*, uint32_t)>;
|
||||||
using slot_tracker = std::function<torrent::tracker::Tracker*(core::Download*, uint32_t)>;
|
using slot_tracker = std::function<torrent::tracker::Tracker(core::Download*, uint32_t)>;
|
||||||
using slot_peer = std::function<torrent::Peer*(core::Download*, const torrent::HashString&)>;
|
using slot_peer = std::function<torrent::Peer*(core::Download*, const torrent::HashString&)>;
|
||||||
using slot_response_callback = std::function<bool(const char*, uint32_t)>;
|
using slot_response_callback = std::function<bool(const char*, uint32_t)>;
|
||||||
|
|
||||||
@@ -58,13 +63,14 @@ public:
|
|||||||
bool process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
|
bool process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
|
||||||
|
|
||||||
void insert_command(const char* name, const char* parm, const char* doc);
|
void insert_command(const char* name, const char* parm, const char* doc);
|
||||||
static void object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target);
|
|
||||||
|
|
||||||
slot_download& slot_find_download() { return m_slot_find_download; }
|
slot_download& slot_find_download() { return m_slot_find_download; }
|
||||||
slot_file& slot_find_file() { return m_slot_find_file; }
|
slot_file& slot_find_file() { return m_slot_find_file; }
|
||||||
slot_tracker& slot_find_tracker() { return m_slot_find_tracker; }
|
slot_tracker& slot_find_tracker() { return m_slot_find_tracker; }
|
||||||
slot_peer& slot_find_peer() { return m_slot_find_peer; }
|
slot_peer& slot_find_peer() { return m_slot_find_peer; }
|
||||||
|
|
||||||
|
static void object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target, std::function<void()>* deleter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
XmlRpc m_xmlrpc;
|
XmlRpc m_xmlrpc;
|
||||||
JsonRpc m_jsonrpc;
|
JsonRpc m_jsonrpc;
|
||||||
@@ -78,6 +84,9 @@ private:
|
|||||||
slot_tracker m_slot_find_tracker;
|
slot_tracker m_slot_find_tracker;
|
||||||
slot_peer m_slot_find_peer;
|
slot_peer m_slot_find_peer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern RpcManager rpc;
|
||||||
|
|
||||||
} // namespace rpc
|
} // namespace rpc
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,39 +1,3 @@
|
|||||||
// rTorrent - BitTorrent client
|
|
||||||
// Copyright (C) 2005-2011, Jari Sundell
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations
|
|
||||||
// including the two.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Contact: Jari Sundell <jaris@ifi.uio.no>
|
|
||||||
//
|
|
||||||
// Skomakerveien 33
|
|
||||||
// 3185 Skoppum, NORWAY
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "xmlrpc.h"
|
#include "xmlrpc.h"
|
||||||
|
|||||||
+2
-1
@@ -4,6 +4,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <torrent/common.h>
|
#include <torrent/common.h>
|
||||||
#include <torrent/hash_string.h>
|
#include <torrent/hash_string.h>
|
||||||
|
#include <torrent/tracker/tracker.h>
|
||||||
|
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "scgi_task.h"
|
#include "scgi_task.h"
|
||||||
@@ -18,7 +19,7 @@ class XmlRpc {
|
|||||||
public:
|
public:
|
||||||
typedef std::function<core::Download* (const char*)> slot_download;
|
typedef std::function<core::Download* (const char*)> slot_download;
|
||||||
typedef std::function<torrent::File* (core::Download*, uint32_t)> slot_file;
|
typedef std::function<torrent::File* (core::Download*, uint32_t)> slot_file;
|
||||||
typedef std::function<torrent::tracker::Tracker* (core::Download*, uint32_t)> slot_tracker;
|
typedef std::function<torrent::tracker::Tracker (core::Download*, uint32_t)> slot_tracker;
|
||||||
typedef std::function<torrent::Peer* (core::Download*, const torrent::HashString&)> slot_peer;
|
typedef std::function<torrent::Peer* (core::Download*, const torrent::HashString&)> slot_peer;
|
||||||
typedef std::function<bool (const char*, uint32_t)> slot_write;
|
typedef std::function<bool (const char*, uint32_t)> slot_write;
|
||||||
|
|
||||||
|
|||||||
+66
-84
@@ -1,39 +1,3 @@
|
|||||||
// rTorrent - BitTorrent client
|
|
||||||
// Copyright (C) 2005-2011, Jari Sundell
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations
|
|
||||||
// including the two.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Contact: Jari Sundell <jaris@ifi.uio.no>
|
|
||||||
//
|
|
||||||
// Skomakerveien 33
|
|
||||||
// 3185 Skoppum, NORWAY
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#ifdef HAVE_XMLRPC_C
|
#ifdef HAVE_XMLRPC_C
|
||||||
@@ -43,17 +7,16 @@
|
|||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <xmlrpc-c/server.h>
|
|
||||||
|
|
||||||
#include <rak/string_manip.h>
|
#include <rak/string_manip.h>
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
#include <torrent/exceptions.h>
|
#include <torrent/exceptions.h>
|
||||||
|
#include <xmlrpc-c/server.h>
|
||||||
|
|
||||||
#include "rpc_manager.h"
|
#include "rpc_manager.h"
|
||||||
#include "xmlrpc.h"
|
#include "xmlrpc.h"
|
||||||
#include "parse_commands.h"
|
#include "parse_commands.h"
|
||||||
|
#include "utils/functional.h"
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
@@ -71,7 +34,7 @@ private:
|
|||||||
const char* m_msg;
|
const char* m_msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType = 0, rpc::target_type* target = NULL);
|
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type = 0, rpc::target_type* target = NULL, std::function<void()>* deleter = NULL);
|
||||||
|
|
||||||
inline torrent::Object
|
inline torrent::Object
|
||||||
xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
|
xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
|
||||||
@@ -135,59 +98,70 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc::target_type
|
std::pair<rpc::target_type, std::function<void()>>
|
||||||
xmlrpc_to_target(xmlrpc_env* env, xmlrpc_value* value, int callType) {
|
xmlrpc_to_target(xmlrpc_env* env, xmlrpc_value* value, int call_type) {
|
||||||
rpc::target_type target;
|
rpc::target_type target;
|
||||||
|
std::function<void()> deleter = []() {};
|
||||||
|
|
||||||
Rpc::object_to_target(xmlrpc_to_object(env, value, -1, nullptr), callType, &target);
|
RpcManager::object_to_target(xmlrpc_to_object(env, value, -1, nullptr), call_type, &target, &deleter);
|
||||||
return target;
|
|
||||||
|
return std::make_pair(target, deleter);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc::target_type
|
std::pair<rpc::target_type, std::function<void()>>
|
||||||
xmlrpc_to_index_type(int index, int callType, core::Download* download) {
|
xmlrpc_to_index_type(int index, int call_type, core::Download* download) {
|
||||||
void* result;
|
void* result = nullptr;
|
||||||
|
std::function<void()> deleter = []() {};
|
||||||
|
|
||||||
switch (callType) {
|
try {
|
||||||
case XmlRpc::call_file: result = xmlrpc.slot_find_file()(download, index); break;
|
|
||||||
case XmlRpc::call_tracker: result = xmlrpc.slot_find_tracker()(download, index); break;
|
switch (call_type) {
|
||||||
default: result = NULL; break;
|
case XmlRpc::call_file:
|
||||||
|
result = rpc.slot_find_file()(download, index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XmlRpc::call_tracker:
|
||||||
|
result = new torrent::tracker::Tracker(rpc.slot_find_tracker()(download, index));
|
||||||
|
deleter = [result]() { delete (torrent::tracker::Tracker*)result; };
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw torrent::input_error("invalid parameters: unexpected target type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const torrent::input_error& e) {
|
||||||
|
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == NULL)
|
return std::make_pair(rpc::make_target(call_type, result), deleter);
|
||||||
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index.");
|
|
||||||
|
|
||||||
return rpc::make_target(callType, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::Object
|
torrent::Object
|
||||||
xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target_type* target) {
|
xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type, rpc::target_type* target, std::function<void()>* deleter) {
|
||||||
switch (xmlrpc_value_type(value)) {
|
switch (xmlrpc_value_type(value)) {
|
||||||
case XMLRPC_TYPE_INT:
|
case XMLRPC_TYPE_INT:
|
||||||
int v;
|
int v;
|
||||||
xmlrpc_read_int(env, value, &v);
|
xmlrpc_read_int(env, value, &v);
|
||||||
|
|
||||||
return torrent::Object((int64_t)v);
|
return torrent::Object((int64_t)v);
|
||||||
|
|
||||||
#ifdef XMLRPC_HAVE_I8
|
#ifdef XMLRPC_HAVE_I8
|
||||||
case XMLRPC_TYPE_I8:
|
case XMLRPC_TYPE_I8:
|
||||||
xmlrpc_int64 v2;
|
xmlrpc_int64 v2;
|
||||||
xmlrpc_read_i8(env, value, &v2);
|
xmlrpc_read_i8(env, value, &v2);
|
||||||
|
|
||||||
return torrent::Object((int64_t)v2);
|
return torrent::Object((int64_t)v2);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// case XMLRPC_TYPE_BOOL:
|
|
||||||
// case XMLRPC_TYPE_DOUBLE:
|
|
||||||
// case XMLRPC_TYPE_DATETIME:
|
|
||||||
|
|
||||||
case XMLRPC_TYPE_STRING:
|
case XMLRPC_TYPE_STRING:
|
||||||
|
|
||||||
if (callType != XmlRpc::call_generic && target != nullptr) {
|
if (call_type != XmlRpc::call_generic && target != nullptr) {
|
||||||
// When the call type is not supposed to be void, we'll try to
|
// When the call type is not supposed to be void, we'll try to
|
||||||
// convert it to a command target. It's not that important that
|
// convert it to a command target. It's not that important that
|
||||||
// it is converted to the right type here, as an mismatch will
|
// it is converted to the right type here, as an mismatch will
|
||||||
// be caught when executing the command.
|
// be caught when executing the command.
|
||||||
*target = xmlrpc_to_target(env, value, callType);
|
std::tie(*target, *deleter) = xmlrpc_to_target(env, value, call_type);
|
||||||
return torrent::Object();
|
return torrent::Object();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -199,7 +173,6 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target
|
|||||||
|
|
||||||
torrent::Object result = torrent::Object(std::string(valueString));
|
torrent::Object result = torrent::Object(std::string(valueString));
|
||||||
|
|
||||||
// Urgh, seriously?
|
|
||||||
::free((void*)valueString);
|
::free((void*)valueString);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -216,7 +189,6 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target
|
|||||||
|
|
||||||
torrent::Object result = torrent::Object(std::string(valueString, valueSize));
|
torrent::Object result = torrent::Object(std::string(valueString, valueSize));
|
||||||
|
|
||||||
// Urgh, seriously?
|
|
||||||
::free((void*)valueString);
|
::free((void*)valueString);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -229,7 +201,7 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target
|
|||||||
if (env->fault_occurred)
|
if (env->fault_occurred)
|
||||||
throw xmlrpc_error_c(env);
|
throw xmlrpc_error_c(env);
|
||||||
|
|
||||||
if (callType != XmlRpc::call_generic && last != 0) {
|
if (call_type != XmlRpc::call_generic && last != 0) {
|
||||||
if (last < 1)
|
if (last < 1)
|
||||||
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Too few arguments.");
|
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Too few arguments.");
|
||||||
|
|
||||||
@@ -240,14 +212,15 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target
|
|||||||
throw xmlrpc_error_c(env);
|
throw xmlrpc_error_c(env);
|
||||||
|
|
||||||
if (target != nullptr)
|
if (target != nullptr)
|
||||||
*target = xmlrpc_to_target(env, tmp, callType);
|
std::tie(*target, *deleter) = xmlrpc_to_target(env, tmp, call_type);
|
||||||
|
|
||||||
xmlrpc_DECREF(tmp);
|
xmlrpc_DECREF(tmp);
|
||||||
|
|
||||||
if (env->fault_occurred)
|
if (env->fault_occurred)
|
||||||
throw xmlrpc_error_c(env);
|
throw xmlrpc_error_c(env);
|
||||||
|
|
||||||
if (target->first == XmlRpc::call_download &&
|
if (target->first == XmlRpc::call_download &&
|
||||||
(callType == XmlRpc::call_file || callType == XmlRpc::call_tracker)) {
|
(call_type == XmlRpc::call_file || call_type == XmlRpc::call_tracker)) {
|
||||||
// If we have a download target and the call type requires
|
// If we have a download target and the call type requires
|
||||||
// another contained type, then we try to use the next
|
// another contained type, then we try to use the next
|
||||||
// parameter as the index to support old-style calls.
|
// parameter as the index to support old-style calls.
|
||||||
@@ -255,7 +228,17 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target
|
|||||||
if (current == last)
|
if (current == last)
|
||||||
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Too few arguments, missing index.");
|
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Too few arguments, missing index.");
|
||||||
|
|
||||||
*target = xmlrpc_to_index_type(xmlrpc_list_entry_to_value(env, value, current++), callType, (core::Download*)target->second);
|
{
|
||||||
|
std::function<void()> old_deleter, tmp_deleter;
|
||||||
|
std::tie(*target, tmp_deleter) = xmlrpc_to_index_type(xmlrpc_list_entry_to_value(env, value, current++),
|
||||||
|
call_type,
|
||||||
|
(core::Download*)target->second);
|
||||||
|
old_deleter.swap(*deleter);
|
||||||
|
*deleter = [old_deleter, tmp_deleter]() {
|
||||||
|
tmp_deleter();
|
||||||
|
old_deleter();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,10 +259,6 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// case XMLRPC_TYPE_STRUCT:
|
|
||||||
// case XMLRPC_TYPE_C_PTR:
|
|
||||||
// case XMLRPC_TYPE_NIL:
|
|
||||||
// case XMLRPC_TYPE_DEAD:
|
|
||||||
default:
|
default:
|
||||||
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Unsupported type found.");
|
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Unsupported type found.");
|
||||||
}
|
}
|
||||||
@@ -291,7 +270,7 @@ object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) {
|
|||||||
case torrent::Object::TYPE_VALUE:
|
case torrent::Object::TYPE_VALUE:
|
||||||
|
|
||||||
#ifdef XMLRPC_HAVE_I8
|
#ifdef XMLRPC_HAVE_I8
|
||||||
if (xmlrpc.dialect() != XmlRpc::dialect_generic)
|
if (rpc.dialect() != XmlRpc::dialect_generic)
|
||||||
return xmlrpc_i8_new(env, object.as_value());
|
return xmlrpc_i8_new(env, object.as_value());
|
||||||
#else
|
#else
|
||||||
return xmlrpc_int_new(env, object.as_value());
|
return xmlrpc_int_new(env, object.as_value());
|
||||||
@@ -329,7 +308,7 @@ object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) {
|
|||||||
case torrent::Object::TYPE_LIST:
|
case torrent::Object::TYPE_LIST:
|
||||||
{
|
{
|
||||||
xmlrpc_value* result = xmlrpc_array_new(env);
|
xmlrpc_value* result = xmlrpc_array_new(env);
|
||||||
|
|
||||||
for (torrent::Object::list_const_iterator itr = object.as_list().begin(), last = object.as_list().end(); itr != last; itr++) {
|
for (torrent::Object::list_const_iterator itr = object.as_list().begin(), last = object.as_list().end(); itr != last; itr++) {
|
||||||
xmlrpc_value* item = object_to_xmlrpc(env, *itr);
|
xmlrpc_value* item = object_to_xmlrpc(env, *itr);
|
||||||
xmlrpc_array_append_item(env, result, item);
|
xmlrpc_array_append_item(env, result, item);
|
||||||
@@ -342,7 +321,7 @@ object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) {
|
|||||||
case torrent::Object::TYPE_MAP:
|
case torrent::Object::TYPE_MAP:
|
||||||
{
|
{
|
||||||
xmlrpc_value* result = xmlrpc_struct_new(env);
|
xmlrpc_value* result = xmlrpc_struct_new(env);
|
||||||
|
|
||||||
for (torrent::Object::map_const_iterator itr = object.as_map().begin(), last = object.as_map().end(); itr != last; itr++) {
|
for (torrent::Object::map_const_iterator itr = object.as_map().begin(), last = object.as_map().end(); itr != last; itr++) {
|
||||||
xmlrpc_value* item = object_to_xmlrpc(env, itr->second);
|
xmlrpc_value* item = object_to_xmlrpc(env, itr->second);
|
||||||
xmlrpc_struct_set_value(env, result, itr->first.c_str(), item);
|
xmlrpc_struct_set_value(env, result, itr->first.c_str(), item);
|
||||||
@@ -355,11 +334,11 @@ object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) {
|
|||||||
case torrent::Object::TYPE_DICT_KEY:
|
case torrent::Object::TYPE_DICT_KEY:
|
||||||
{
|
{
|
||||||
xmlrpc_value* result = xmlrpc_array_new(env);
|
xmlrpc_value* result = xmlrpc_array_new(env);
|
||||||
|
|
||||||
xmlrpc_value* key_item = object_to_xmlrpc(env, object.as_dict_key());
|
xmlrpc_value* key_item = object_to_xmlrpc(env, object.as_dict_key());
|
||||||
xmlrpc_array_append_item(env, result, key_item);
|
xmlrpc_array_append_item(env, result, key_item);
|
||||||
xmlrpc_DECREF(key_item);
|
xmlrpc_DECREF(key_item);
|
||||||
|
|
||||||
if (object.as_dict_obj().is_list()) {
|
if (object.as_dict_obj().is_list()) {
|
||||||
for (torrent::Object::list_const_iterator
|
for (torrent::Object::list_const_iterator
|
||||||
itr = object.as_dict_obj().as_list().begin(),
|
itr = object.as_dict_obj().as_list().begin(),
|
||||||
@@ -392,18 +371,21 @@ xmlrpc_call_command(xmlrpc_env* env, xmlrpc_value* args, void* voidServerInfo) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::function<void()> deleter = []() {};
|
||||||
|
utils::scope_guard guard([&deleter]() { deleter(); });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
torrent::Object object;
|
torrent::Object object;
|
||||||
rpc::target_type target = rpc::make_target();
|
rpc::target_type target = rpc::make_target();
|
||||||
|
|
||||||
if (itr->second.m_flags & CommandMap::flag_no_target)
|
if (itr->second.m_flags & CommandMap::flag_no_target)
|
||||||
xmlrpc_to_object(env, args, XmlRpc::call_generic, &target).swap(object);
|
xmlrpc_to_object(env, args, XmlRpc::call_generic, &target, &deleter).swap(object);
|
||||||
else if (itr->second.m_flags & CommandMap::flag_file_target)
|
else if (itr->second.m_flags & CommandMap::flag_file_target)
|
||||||
xmlrpc_to_object(env, args, XmlRpc::call_file, &target).swap(object);
|
xmlrpc_to_object(env, args, XmlRpc::call_file, &target, &deleter).swap(object);
|
||||||
else if (itr->second.m_flags & CommandMap::flag_tracker_target)
|
else if (itr->second.m_flags & CommandMap::flag_tracker_target)
|
||||||
xmlrpc_to_object(env, args, XmlRpc::call_tracker, &target).swap(object);
|
xmlrpc_to_object(env, args, XmlRpc::call_tracker, &target, &deleter).swap(object);
|
||||||
else
|
else
|
||||||
xmlrpc_to_object(env, args, XmlRpc::call_any, &target).swap(object);
|
xmlrpc_to_object(env, args, XmlRpc::call_any, &target, &deleter).swap(object);
|
||||||
|
|
||||||
if (env->fault_occurred)
|
if (env->fault_occurred)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -425,7 +407,7 @@ XmlRpc::initialize() {
|
|||||||
#ifndef XMLRPC_HAVE_I8
|
#ifndef XMLRPC_HAVE_I8
|
||||||
m_dialect = dialect_generic;
|
m_dialect = dialect_generic;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_env = new xmlrpc_env;
|
m_env = new xmlrpc_env;
|
||||||
|
|
||||||
xmlrpc_env_init((xmlrpc_env*)m_env);
|
xmlrpc_env_init((xmlrpc_env*)m_env);
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ ElementTrackerList::receive_disable() {
|
|||||||
|
|
||||||
auto t = m_download->download()->tracker_list()->at(m_focus);
|
auto t = m_download->download()->tracker_list()->at(m_focus);
|
||||||
|
|
||||||
if (t->is_enabled())
|
if (t.is_enabled())
|
||||||
t->disable();
|
t.disable();
|
||||||
else
|
else
|
||||||
t->enable();
|
t.enable();
|
||||||
|
|
||||||
m_window->mark_dirty();
|
m_window->mark_dirty();
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ ElementTrackerList::receive_cycle_group() {
|
|||||||
if (m_focus >= tl->size())
|
if (m_focus >= tl->size())
|
||||||
throw torrent::internal_error("ui::ElementTrackerList::receive_group_cycle(...) called with an invalid focus");
|
throw torrent::internal_error("ui::ElementTrackerList::receive_group_cycle(...) called with an invalid focus");
|
||||||
|
|
||||||
tl->cycle_group(tl->at(m_focus)->group());
|
tl->cycle_group(tl->at(m_focus).group());
|
||||||
|
|
||||||
m_window->mark_dirty();
|
m_window->mark_dirty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef RTORRENT_UTILS_FUNCTIONAL_H
|
||||||
|
#define RTORRENT_UTILS_FUNCTIONAL_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace utils {
|
||||||
|
|
||||||
|
// Create a class that calls a std::function when returning or exiting a scope.
|
||||||
|
class scope_guard {
|
||||||
|
public:
|
||||||
|
scope_guard(std::function<void()> on_exit_scope) : m_on_exit_scope(on_exit_scope) {}
|
||||||
|
~scope_guard() { m_on_exit_scope(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<void()> m_on_exit_scope;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace utils
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
Reference in New Issue
Block a user