mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-06 18:22:32 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2700b3141f | |||
| 4fec56a243 | |||
| dcf24711ef | |||
| 4e3ad1ab62 | |||
| b5a606649a | |||
| eb96876eb2 | |||
| a964b0a350 | |||
| 1adcf230d2 | |||
| 4235507292 | |||
| b7a21b1e9d | |||
| bd53959b40 | |||
| 5a4ba8bc32 | |||
| e64ec358fa | |||
| b78fd9aef5 | |||
| 21e399d099 | |||
| 08a907b547 | |||
| a0b8702895 | |||
| ad3c31862e | |||
| 2711e4c044 | |||
| 8d50aee96f | |||
| 71bb51c3fd | |||
| 63ea08bde6 | |||
| a82bdf22ac | |||
| 9f6731b4e2 | |||
| d935e0ffe9 | |||
| ba239bc8c5 | |||
| ea16276773 | |||
| f767053297 | |||
| 598914908f | |||
| 38fc815d52 | |||
| 674ae767aa | |||
| 0aaa470053 | |||
| 6f27159627 | |||
| de163293ab | |||
| fb4e775689 |
@@ -16,7 +16,8 @@ jobs:
|
||||
sudo apt-get install -y \
|
||||
bear \
|
||||
clang-tidy \
|
||||
libcurl4-openssl-dev
|
||||
libcurl4-openssl-dev \
|
||||
zlib1g-dev
|
||||
- name: Fetch libtorrent
|
||||
run: |
|
||||
git clone https://github.com/rakshasa/libtorrent
|
||||
|
||||
@@ -15,6 +15,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get install -y \
|
||||
libcppunit-dev \
|
||||
zlib1g-dev \
|
||||
libcurl4-openssl-dev
|
||||
- name: Fetch libtorrent
|
||||
run: |
|
||||
|
||||
@@ -7,12 +7,7 @@ nobase_dist_pkgdata_DATA = \
|
||||
lua/rtorrent.lua
|
||||
|
||||
EXTRA_DIST= \
|
||||
rak/address_info.h \
|
||||
rak/algorithm.h \
|
||||
rak/partial_queue.h \
|
||||
rak/regex.h \
|
||||
rak/string_manip.h \
|
||||
rak/unordered_vector.h \
|
||||
scripts/checks.m4 \
|
||||
scripts/common.m4 \
|
||||
scripts/attributes.m4
|
||||
|
||||
+8
-6
@@ -1,6 +1,6 @@
|
||||
m4_pattern_allow([PKG_CHECK_EXISTS])
|
||||
|
||||
AC_INIT([rtorrent],[0.16.8],[sundell.software@gmail.com])
|
||||
AC_INIT([rtorrent],[0.16.11],[sundell.software@gmail.com])
|
||||
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_MACRO_DIRS([scripts])
|
||||
@@ -11,7 +11,7 @@ LT_INIT
|
||||
|
||||
AC_PROG_CXX
|
||||
|
||||
AC_DEFINE([API_VERSION], [19], [api version])
|
||||
AC_DEFINE([API_VERSION], [20], [api version])
|
||||
|
||||
# Filter out unwanted flags added by autoconf on some systems, e.g. MacOS.
|
||||
TORRENT_REMOVE_UNWANTED(CXX, $CXX, -std=c++11 -std=gnu++11)
|
||||
@@ -48,7 +48,8 @@ if test "x$ax_cv_ncursesw" != xyes && test "x$ax_cv_ncurses" != xyes; then
|
||||
fi
|
||||
|
||||
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
|
||||
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.8])
|
||||
PKG_CHECK_MODULES([ZLIB], [zlib])
|
||||
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.11])
|
||||
|
||||
AC_LANG_PUSH(C++)
|
||||
TORRENT_WITH_XMLRPC_C
|
||||
@@ -56,6 +57,7 @@ AC_LANG_POP(C++)
|
||||
|
||||
TORRENT_WITH_LUA
|
||||
TORRENT_WITH_TINYXML2
|
||||
TORRENT_WITH_SYSTEMD
|
||||
|
||||
if test ${with_xmlrpc_c+y} && test ${with_xmlrpc_tinyxml2+y}; then
|
||||
AC_MSG_ERROR([--with-xmlrpc-c and --with-xmlrpc-tinyxml2 cannot be used together. Please choose only one])
|
||||
@@ -65,9 +67,9 @@ AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION "/") + torrent::version()
|
||||
|
||||
dnl Only update global build variables immediately before generating the output,
|
||||
dnl to avoid affecting the global build environment for other autoconf checks.
|
||||
LIBS="$PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $DEPENDENCIES_LIBS $LIBS"
|
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $DEPENDENCIES_CFLAGS $CURSES_CFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $DEPENDENCIES_CFLAGS $CURSES_CFLAGS"
|
||||
LIBS="$PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS"
|
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
|
||||
|
||||
TORRENT_CHECK_POPCOUNT()
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// Wrapper for addrinfo with focus on zero-copy conversion to and from
|
||||
// the c-type and wrapper.
|
||||
//
|
||||
// Do use the wrapper on a pre-existing struct addrinfo, cast the
|
||||
// pointer rather than the base type.
|
||||
|
||||
#ifndef RAK_ADDRESS_INFO_H
|
||||
#define RAK_ADDRESS_INFO_H
|
||||
|
||||
#include <netdb.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace rak {
|
||||
|
||||
class address_info {
|
||||
public:
|
||||
void clear() { std::memset(this, 0, sizeof(address_info)); }
|
||||
|
||||
int flags() const { return m_addrinfo.ai_flags; }
|
||||
void set_flags(int f) { m_addrinfo.ai_flags = f; }
|
||||
|
||||
int family() const { return m_addrinfo.ai_family; }
|
||||
void set_family(int f) { m_addrinfo.ai_family = f; }
|
||||
|
||||
int socket_type() const { return m_addrinfo.ai_socktype; }
|
||||
void set_socket_type(int t) { m_addrinfo.ai_socktype = t; }
|
||||
|
||||
int protocol() const { return m_addrinfo.ai_protocol; }
|
||||
void set_protocol(int p) { m_addrinfo.ai_protocol = p; }
|
||||
|
||||
size_t length() const { return m_addrinfo.ai_addrlen; }
|
||||
|
||||
addrinfo* c_addrinfo() { return &m_addrinfo; }
|
||||
const addrinfo* c_addrinfo() const { return &m_addrinfo; }
|
||||
|
||||
address_info* next() { return reinterpret_cast<address_info*>(m_addrinfo.ai_next); }
|
||||
|
||||
static int get_address_info(const char* node, int domain, int type, address_info** ai);
|
||||
|
||||
static void free_address_info(address_info* ai) { ::freeaddrinfo(ai->c_addrinfo()); }
|
||||
|
||||
static const char* strerror(int err) { return gai_strerror(err); }
|
||||
|
||||
private:
|
||||
addrinfo m_addrinfo;
|
||||
};
|
||||
|
||||
inline int
|
||||
address_info::get_address_info(const char* node, int pfamily, int stype, address_info** ai) {
|
||||
address_info hints;
|
||||
hints.clear();
|
||||
hints.set_family(pfamily);
|
||||
hints.set_socket_type(stype);
|
||||
|
||||
return ::getaddrinfo(node, NULL, hints.c_addrinfo(), reinterpret_cast<addrinfo**>(ai));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-195
@@ -1,195 +0,0 @@
|
||||
// rak - Rakshasa's toolbox
|
||||
// Copyright (C) 2005-2007, 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RAK_ALGORITHM_H
|
||||
#define RAK_ALGORITHM_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace rak {
|
||||
|
||||
template <typename _InputIter, typename _Function>
|
||||
_Function
|
||||
for_each_pre(_InputIter __first, _InputIter __last, _Function __f) {
|
||||
_InputIter __tmp;
|
||||
|
||||
while (__first != __last) {
|
||||
__tmp = __first++;
|
||||
|
||||
__f(*__tmp);
|
||||
}
|
||||
|
||||
return __f;
|
||||
}
|
||||
|
||||
// Return a range with a distance of no more than __distance and
|
||||
// between __first and __last, centered on __middle1.
|
||||
template <typename _InputIter, typename _Distance>
|
||||
std::pair<_InputIter, _InputIter>
|
||||
advance_bidirectional(_InputIter __first, _InputIter __middle1, _InputIter __last, _Distance __distance) {
|
||||
_InputIter __middle2 = __middle1;
|
||||
|
||||
do {
|
||||
if (!__distance)
|
||||
break;
|
||||
|
||||
if (__middle2 != __last) {
|
||||
++__middle2;
|
||||
--__distance;
|
||||
|
||||
} else if (__middle1 == __first) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!__distance)
|
||||
break;
|
||||
|
||||
if (__middle1 != __first) {
|
||||
--__middle1;
|
||||
--__distance;
|
||||
|
||||
} else if (__middle2 == __last) {
|
||||
break;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
return std::make_pair(__middle1, __middle2);
|
||||
}
|
||||
|
||||
template <typename _InputIter, typename _Distance>
|
||||
_InputIter
|
||||
advance_forward(_InputIter __first, _InputIter __last, _Distance __distance) {
|
||||
while (__first != __last && __distance != 0) {
|
||||
__first++;
|
||||
__distance--;
|
||||
}
|
||||
|
||||
return __first;
|
||||
}
|
||||
|
||||
template <typename _InputIter, typename _Distance>
|
||||
_InputIter
|
||||
advance_backward(_InputIter __first, _InputIter __last, _Distance __distance) {
|
||||
while (__first != __last && __distance != 0) {
|
||||
__first--;
|
||||
__distance--;
|
||||
}
|
||||
|
||||
return __first;
|
||||
}
|
||||
|
||||
template <typename _Value>
|
||||
struct compare_base {
|
||||
bool operator () (const _Value& complete, const _Value& base) const {
|
||||
return !complete.compare(0, base.size(), base);
|
||||
}
|
||||
};
|
||||
|
||||
// Count the number of elements from the start of the containers to
|
||||
// the first inequal element.
|
||||
template <typename _InputIter1, typename _InputIter2>
|
||||
typename std::iterator_traits<_InputIter1>::difference_type
|
||||
count_base(_InputIter1 __first1, _InputIter1 __last1,
|
||||
_InputIter2 __first2, _InputIter2 __last2) {
|
||||
|
||||
typename std::iterator_traits<_InputIter1>::difference_type __n = 0;
|
||||
|
||||
for ( ;__first1 != __last1 && __first2 != __last2; ++__first1, ++__first2, ++__n)
|
||||
if (*__first1 != *__first2)
|
||||
return __n;
|
||||
|
||||
return __n;
|
||||
}
|
||||
|
||||
template <typename _Return, typename _InputIter, typename _Ftor>
|
||||
_Return
|
||||
make_base(_InputIter __first, _InputIter __last, _Ftor __ftor) {
|
||||
if (__first == __last)
|
||||
return "";
|
||||
|
||||
_Return __base = __ftor(*__first++);
|
||||
|
||||
for ( ;__first != __last; ++__first) {
|
||||
typename std::iterator_traits<_InputIter>::difference_type __pos = count_base(__base.begin(), __base.end(),
|
||||
__ftor(*__first).begin(), __ftor(*__first).end());
|
||||
|
||||
if (__pos < (typename std::iterator_traits<_InputIter>::difference_type)__base.size())
|
||||
__base.resize(__pos);
|
||||
}
|
||||
|
||||
return __base;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline int popcount_wrapper(T t) {
|
||||
#if USE_BUILTIN_POPCOUNT
|
||||
return __builtin_popcountll(t);
|
||||
#else
|
||||
#error __builtin_popcount not found.
|
||||
unsigned int count = 0;
|
||||
|
||||
while (t) {
|
||||
count += t & 0x1;
|
||||
t >> 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Get the median of an unordered set of numbers of arbitrary
|
||||
// type by modifing the underlying dataset
|
||||
template <typename T = double, typename _InputIter>
|
||||
T median(_InputIter __first, _InputIter __last) {
|
||||
T __med;
|
||||
|
||||
unsigned int __size = __last - __first;
|
||||
unsigned int __middle = __size / 2;
|
||||
_InputIter __target1 = __first + __middle;
|
||||
std::nth_element(__first, __target1, __last);
|
||||
__med = *__target1;
|
||||
|
||||
if (__size % 2 == 0) {
|
||||
_InputIter __target2 = std::max_element(__first, __target1);
|
||||
__med = (__med + *__target2) / 2.0;
|
||||
}
|
||||
|
||||
return __med;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,197 +0,0 @@
|
||||
// rak - Rakshasa's toolbox
|
||||
// Copyright (C) 2005-2007, 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RAK_PARTIAL_QUEUE_H
|
||||
#define RAK_PARTIAL_QUEUE_H
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace rak {
|
||||
|
||||
// First step, don't allow overflowing to the next layer. Only disable
|
||||
// the above layers for now.
|
||||
|
||||
// We also include 0 in a single layer as some chunk may be available
|
||||
// only through seeders.
|
||||
|
||||
class partial_queue {
|
||||
public:
|
||||
typedef uint8_t key_type;
|
||||
typedef uint32_t mapped_type;
|
||||
typedef uint16_t size_type;
|
||||
typedef std::pair<size_type, size_type> size_pair_type;
|
||||
|
||||
static const size_type num_layers = 8;
|
||||
|
||||
partial_queue() : m_data(NULL), m_maxLayerSize(0) {}
|
||||
~partial_queue() { disable(); }
|
||||
|
||||
bool is_full() const { return m_ceiling == 0; }
|
||||
bool is_layer_full(size_type l) const { return m_layers[l].second >= m_maxLayerSize; }
|
||||
|
||||
bool is_enabled() const { return m_data != NULL; }
|
||||
|
||||
// Add check to see if we can add more. Also make it possible to
|
||||
// check how full we are in the lower parts so the caller knows when
|
||||
// he can stop searching.
|
||||
//
|
||||
// Though propably not needed, as we must continue til the first
|
||||
// layer is full.
|
||||
|
||||
size_type max_size() const { return m_maxLayerSize * num_layers; }
|
||||
size_type max_layer_size() const { return m_maxLayerSize; }
|
||||
|
||||
// Must be less that or equal to (max size_type) / num_layers.
|
||||
void enable(size_type ls);
|
||||
void disable();
|
||||
|
||||
void clear();
|
||||
|
||||
// Safe to call while pop'ing and it will not reuse pop'ed indices
|
||||
// so it is guaranteed to reach max_size at some point. This will
|
||||
// ensure that the user needs to refill with new data at regular
|
||||
// intervals.
|
||||
bool insert(key_type key, mapped_type value);
|
||||
|
||||
// Only call this when pop'ing as it moves the index.
|
||||
bool prepare_pop();
|
||||
|
||||
mapped_type pop();
|
||||
|
||||
private:
|
||||
partial_queue(const partial_queue&);
|
||||
void operator = (const partial_queue&);
|
||||
|
||||
static size_type ceiling(size_type layer) { return (2 << layer) - 1; }
|
||||
|
||||
void find_non_empty();
|
||||
|
||||
mapped_type* m_data;
|
||||
size_type m_maxLayerSize;
|
||||
|
||||
size_type m_index;
|
||||
size_type m_ceiling;
|
||||
|
||||
std::array<size_pair_type, num_layers> m_layers;
|
||||
};
|
||||
|
||||
inline void
|
||||
partial_queue::enable(size_type ls) {
|
||||
if (ls == 0)
|
||||
throw std::logic_error("partial_queue::enable(...) ls == 0.");
|
||||
|
||||
delete [] m_data;
|
||||
m_data = new mapped_type[ls * num_layers];
|
||||
|
||||
m_maxLayerSize = ls;
|
||||
}
|
||||
|
||||
inline void
|
||||
partial_queue::disable() {
|
||||
delete [] m_data;
|
||||
m_data = NULL;
|
||||
|
||||
m_maxLayerSize = 0;
|
||||
}
|
||||
|
||||
inline void
|
||||
partial_queue::clear() {
|
||||
if (m_data == NULL)
|
||||
return;
|
||||
|
||||
m_index = 0;
|
||||
m_ceiling = ceiling(num_layers - 1);
|
||||
|
||||
m_layers = {};
|
||||
}
|
||||
|
||||
inline bool
|
||||
partial_queue::insert(key_type key, mapped_type value) {
|
||||
if (key >= m_ceiling)
|
||||
return false;
|
||||
|
||||
size_type idx = 0;
|
||||
|
||||
// Hmm... since we already check the 'm_ceiling' above, we only need
|
||||
// to find the target layer. Could this be calculated directly?
|
||||
while (key >= ceiling(idx))
|
||||
++idx;
|
||||
|
||||
m_index = std::min(m_index, idx);
|
||||
|
||||
// Currently don't allow overflow.
|
||||
if (is_layer_full(idx))
|
||||
throw std::logic_error("partial_queue::insert(...) layer already full.");
|
||||
//return false;
|
||||
|
||||
m_data[m_maxLayerSize * idx + m_layers[idx].second] = value;
|
||||
m_layers[idx].second++;
|
||||
|
||||
if (is_layer_full(idx))
|
||||
// Set the ceiling to 0 when layer 0 is full so no more values can
|
||||
// be inserted.
|
||||
m_ceiling = idx > 0 ? ceiling(idx - 1) : 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// is_empty() will iterate to the first layer with un-popped elements
|
||||
// and return true, else return false when it reaches a overflowed or
|
||||
// the last layer.
|
||||
inline bool
|
||||
partial_queue::prepare_pop() {
|
||||
while (m_layers[m_index].first == m_layers[m_index].second) {
|
||||
if (is_layer_full(m_index) || m_index + 1 == num_layers)
|
||||
return false;
|
||||
|
||||
m_index++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline partial_queue::mapped_type
|
||||
partial_queue::pop() {
|
||||
if (m_index >= num_layers || m_layers[m_index].first >= m_layers[m_index].second)
|
||||
throw std::logic_error("partial_queue::pop() bad state.");
|
||||
|
||||
return m_data[m_index * m_maxLayerSize + m_layers[m_index].first++];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,425 +0,0 @@
|
||||
// rak - Rakshasa's toolbox
|
||||
// Copyright (C) 2005-2007, 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RAK_STRING_MANIP_H
|
||||
#define RAK_STRING_MANIP_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <locale>
|
||||
#include <random>
|
||||
|
||||
|
||||
namespace rak {
|
||||
|
||||
// Use these trim functions until n1872 is widely supported.
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence trim_begin(const Sequence& seq) {
|
||||
if (seq.empty() || !std::isspace(*seq.begin()))
|
||||
return seq;
|
||||
|
||||
typename Sequence::size_type pos = 0;
|
||||
|
||||
while (pos != seq.length() && std::isspace(seq[pos]))
|
||||
pos++;
|
||||
|
||||
return seq.substr(pos, seq.length() - pos);
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence trim_end(const Sequence& seq) {
|
||||
if (seq.empty() || !std::isspace(*(--seq.end())))
|
||||
return seq;
|
||||
|
||||
typename Sequence::size_type pos = seq.size();
|
||||
|
||||
while (pos != 0 && std::isspace(seq[pos - 1]))
|
||||
pos--;
|
||||
|
||||
return seq.substr(0, pos);
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence trim(const Sequence& seq) {
|
||||
return trim_begin(trim_end(seq));
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence trim_begin_classic(const Sequence& seq) {
|
||||
if (seq.empty() || !std::isspace(*seq.begin(), std::locale::classic()))
|
||||
return seq;
|
||||
|
||||
typename Sequence::size_type pos = 0;
|
||||
|
||||
while (pos != seq.length() && std::isspace(seq[pos], std::locale::classic()))
|
||||
pos++;
|
||||
|
||||
return seq.substr(pos, seq.length() - pos);
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence trim_end_classic(const Sequence& seq) {
|
||||
if (seq.empty() || !std::isspace(*(--seq.end()), std::locale::classic()))
|
||||
return seq;
|
||||
|
||||
typename Sequence::size_type pos = seq.size();
|
||||
|
||||
while (pos != 0 && std::isspace(seq[pos - 1], std::locale::classic()))
|
||||
pos--;
|
||||
|
||||
return seq.substr(0, pos);
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence trim_classic(const Sequence& seq) {
|
||||
return trim_begin_classic(trim_end_classic(seq));
|
||||
}
|
||||
|
||||
// Consider rewritting such that m_seq is replaced by first/last.
|
||||
template <typename Sequence>
|
||||
class split_iterator_t {
|
||||
public:
|
||||
typedef typename Sequence::const_iterator const_iterator;
|
||||
typedef typename Sequence::value_type value_type;
|
||||
|
||||
split_iterator_t() {}
|
||||
|
||||
split_iterator_t(const Sequence& seq, value_type delim) :
|
||||
m_seq(&seq),
|
||||
m_delim(delim),
|
||||
m_pos(seq.begin()),
|
||||
m_next(std::find(seq.begin(), seq.end(), delim)) {
|
||||
}
|
||||
|
||||
Sequence operator * () { return Sequence(m_pos, m_next); }
|
||||
|
||||
split_iterator_t& operator ++ () {
|
||||
m_pos = m_next;
|
||||
|
||||
if (m_pos == m_seq->end())
|
||||
return *this;
|
||||
|
||||
m_pos++;
|
||||
m_next = std::find(m_pos, m_seq->end(), m_delim);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator == (const split_iterator_t&) const { return m_pos == m_seq->end(); }
|
||||
bool operator != (const split_iterator_t&) const { return m_pos != m_seq->end(); }
|
||||
|
||||
private:
|
||||
const Sequence* m_seq;
|
||||
value_type m_delim;
|
||||
const_iterator m_pos;
|
||||
const_iterator m_next;
|
||||
};
|
||||
|
||||
template <typename Sequence>
|
||||
inline split_iterator_t<Sequence>
|
||||
split_iterator(const Sequence& seq, typename Sequence::value_type delim) {
|
||||
return split_iterator_t<Sequence>(seq, delim);
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline split_iterator_t<Sequence>
|
||||
split_iterator(const Sequence&) {
|
||||
return split_iterator_t<Sequence>();
|
||||
}
|
||||
|
||||
// Could optimize this abit.
|
||||
inline char
|
||||
hexchar_to_value(char c) {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
return 10 + c - 'A';
|
||||
|
||||
else
|
||||
return 10 + c - 'a';
|
||||
}
|
||||
|
||||
template <int pos, typename Value>
|
||||
inline char
|
||||
value_to_hexchar(Value v) {
|
||||
v >>= pos * 4;
|
||||
v &= 0xf;
|
||||
|
||||
if (v < 0xA)
|
||||
return '0' + v;
|
||||
else
|
||||
return 'A' + v - 0xA;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
OutputIterator
|
||||
copy_escape_html(InputIterator first, InputIterator last, OutputIterator dest) {
|
||||
while (first != last) {
|
||||
if (std::isalpha(*first, std::locale::classic()) ||
|
||||
std::isdigit(*first, std::locale::classic()) ||
|
||||
*first == '-') {
|
||||
*(dest++) = *first;
|
||||
|
||||
} else {
|
||||
*(dest++) = '%';
|
||||
*(dest++) = value_to_hexchar<1>(*first);
|
||||
*(dest++) = value_to_hexchar<0>(*first);
|
||||
}
|
||||
|
||||
++first;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
OutputIterator
|
||||
copy_escape_html(InputIterator first1, InputIterator last1, OutputIterator first2, OutputIterator last2) {
|
||||
while (first1 != last1) {
|
||||
if (std::isalpha(*first1, std::locale::classic()) ||
|
||||
std::isdigit(*first1, std::locale::classic()) ||
|
||||
*first1 == '-') {
|
||||
if (first2 == last2) break; else *(first2++) = *first1;
|
||||
|
||||
} else {
|
||||
if (first2 == last2) break; else *(first2++) = '%';
|
||||
if (first2 == last2) break; else *(first2++) = value_to_hexchar<1>(*first1);
|
||||
if (first2 == last2) break; else *(first2++) = value_to_hexchar<0>(*first1);
|
||||
}
|
||||
|
||||
++first1;
|
||||
}
|
||||
|
||||
return first2;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
inline std::string
|
||||
copy_escape_html(Iterator first, Iterator last) {
|
||||
std::string dest;
|
||||
copy_escape_html(first, last, std::back_inserter(dest));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline Sequence
|
||||
copy_escape_html(const Sequence& src) {
|
||||
Sequence dest;
|
||||
copy_escape_html(src.begin(), src.end(), std::back_inserter(dest));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline std::string
|
||||
copy_escape_html_str(const Sequence& src) {
|
||||
std::string dest;
|
||||
copy_escape_html(src.begin(), src.end(), std::back_inserter(dest));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
// Consider support for larger than char type.
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
OutputIterator
|
||||
transform_hex(InputIterator first, InputIterator last, OutputIterator dest) {
|
||||
while (first != last) {
|
||||
*(dest++) = value_to_hexchar<1>(*first);
|
||||
*(dest++) = value_to_hexchar<0>(*first);
|
||||
|
||||
++first;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
OutputIterator
|
||||
transform_hex(InputIterator first1, InputIterator last1, OutputIterator first2, OutputIterator last2) {
|
||||
while (first1 != last1) {
|
||||
if (first2 == last2) break; else *(first2++) = value_to_hexchar<1>(*first1);
|
||||
if (first2 == last2) break; else *(first2++) = value_to_hexchar<0>(*first1);
|
||||
|
||||
++first1;
|
||||
}
|
||||
|
||||
return first2;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline Sequence
|
||||
transform_hex(const Sequence& src) {
|
||||
Sequence dest;
|
||||
transform_hex(src.begin(), src.end(), std::back_inserter(dest));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
inline std::string
|
||||
transform_hex(Iterator first, Iterator last) {
|
||||
std::string dest;
|
||||
transform_hex(first, last, std::back_inserter(dest));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline std::string
|
||||
transform_hex_str(const Sequence& seq) {
|
||||
std::string dest;
|
||||
transform_hex(seq.begin(), seq.end(), std::back_inserter(dest));
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
Sequence
|
||||
generate_random(size_t length) {
|
||||
std::random_device rd;
|
||||
std::mt19937 mt(rd());
|
||||
using bytes_randomizer = std::independent_bits_engine<std::mt19937, CHAR_BIT, uint8_t>;
|
||||
bytes_randomizer bytes(mt);
|
||||
Sequence s;
|
||||
s.reserve(length);
|
||||
std::generate_n(std::back_inserter(s), length, std::ref(bytes));
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
inline bool
|
||||
is_all_alpha(Iterator first, Iterator last) {
|
||||
while (first != last)
|
||||
if (!std::isalpha(*first++, std::locale::classic()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline bool
|
||||
is_all_alpha(const Sequence& src) {
|
||||
return is_all_alpha(src.begin(), src.end());
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
inline bool
|
||||
is_all_alnum(Iterator first, Iterator last) {
|
||||
while (first != last)
|
||||
if (!std::isalnum(*first++, std::locale::classic()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline bool
|
||||
is_all_alnum(const Sequence& src) {
|
||||
return is_all_alnum(src.begin(), src.end());
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
inline bool
|
||||
is_all_name(Iterator first, Iterator last) {
|
||||
while (first != last) {
|
||||
if (!std::isalnum(*first, std::locale::classic()) && *first != '_')
|
||||
return false;
|
||||
|
||||
first++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline bool
|
||||
is_all_name(const Sequence& src) {
|
||||
return is_all_name(src.begin(), src.end());
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
std::string
|
||||
sanitize(Iterator first, Iterator last) {
|
||||
std::string dest;
|
||||
for (; first != last; ++first) {
|
||||
if (std::isprint(*first) && *first != '\r' && *first != '\n' && *first != '\t')
|
||||
dest += *first;
|
||||
else
|
||||
dest += " ";
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
std::string
|
||||
sanitize(const Sequence& src) {
|
||||
return trim(sanitize(src.begin(), src.end()));
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
std::string striptags(Iterator first, Iterator last) {
|
||||
bool copychar = true;
|
||||
std::string dest;
|
||||
|
||||
for (; first != last; ++first) {
|
||||
if (std::isprint(*first) && *first == '<') {
|
||||
copychar = false;
|
||||
} else if (std::isprint(*first) && *first == '>') {
|
||||
copychar = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (copychar)
|
||||
dest += *first;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
std::string striptags(const Sequence& src) {
|
||||
return striptags(src.begin(), src.end());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,107 +0,0 @@
|
||||
// rak - Rakshasa's toolbox
|
||||
// Copyright (C) 2005-2007, 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RAK_UNORDERED_VECTOR_H
|
||||
#define RAK_UNORDERED_VECTOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace rak {
|
||||
|
||||
template <typename _Tp>
|
||||
class unordered_vector : private std::vector<_Tp> {
|
||||
public:
|
||||
typedef std::vector<_Tp> Base;
|
||||
|
||||
typedef typename Base::value_type value_type;
|
||||
typedef typename Base::pointer pointer;
|
||||
typedef typename Base::const_pointer const_pointer;
|
||||
typedef typename Base::reference reference;
|
||||
typedef typename Base::const_reference const_reference;
|
||||
typedef typename Base::size_type size_type;
|
||||
typedef typename Base::difference_type difference_type;
|
||||
typedef typename Base::allocator_type allocator_type;
|
||||
|
||||
typedef typename Base::iterator iterator;
|
||||
typedef typename Base::reverse_iterator reverse_iterator;
|
||||
typedef typename Base::const_iterator const_iterator;
|
||||
typedef typename Base::const_reverse_iterator const_reverse_iterator;
|
||||
|
||||
using Base::clear;
|
||||
using Base::empty;
|
||||
using Base::size;
|
||||
using Base::reserve;
|
||||
|
||||
using Base::front;
|
||||
using Base::back;
|
||||
using Base::begin;
|
||||
using Base::end;
|
||||
using Base::rbegin;
|
||||
using Base::rend;
|
||||
|
||||
using Base::push_back;
|
||||
using Base::pop_back;
|
||||
|
||||
// Use the range erase function, the single element erase gets
|
||||
// overloaded.
|
||||
using Base::erase;
|
||||
|
||||
iterator insert(iterator position, const value_type& x);
|
||||
iterator erase(iterator position);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
template <typename _Tp>
|
||||
typename unordered_vector<_Tp>::iterator
|
||||
unordered_vector<_Tp>::insert(iterator position, const value_type& x) {
|
||||
Base::push_back(x);
|
||||
|
||||
return --end();
|
||||
}
|
||||
|
||||
template <typename _Tp>
|
||||
typename unordered_vector<_Tp>::iterator
|
||||
unordered_vector<_Tp>::erase(iterator position) {
|
||||
// We don't need to check if position == end - 1 since we then copy
|
||||
// to the position we pop later.
|
||||
*position = Base::back();
|
||||
Base::pop_back();
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -358,3 +358,20 @@ AC_DEFUN([TORRENT_DISABLE_PTHREAD_SETNAME_NP], [
|
||||
]
|
||||
)
|
||||
])
|
||||
|
||||
|
||||
AC_DEFUN([TORRENT_WITH_SYSTEMD], [
|
||||
AC_ARG_WITH(systemd,
|
||||
AS_HELP_STRING([--with-systemd],[enable systemd socket activation support [[default=no]]]),
|
||||
[
|
||||
if test "$withval" = "yes"; then
|
||||
PKG_CHECK_MODULES([SYSTEMD], [libsystemd],
|
||||
[
|
||||
CXXFLAGS="$CXXFLAGS $SYSTEMD_CFLAGS"
|
||||
LIBS="$LIBS $SYSTEMD_LIBS"
|
||||
AC_DEFINE(HAVE_SYSTEMD, 1, [Support for systemd socket activation.])
|
||||
],
|
||||
[AC_MSG_ERROR([libsystemd not found. Install libsystemd-dev (or the equivalent for your distribution).])])
|
||||
fi
|
||||
])
|
||||
])
|
||||
|
||||
@@ -169,6 +169,8 @@ libsub_root_a_SOURCES = \
|
||||
utils/file_status_cache.cc \
|
||||
utils/file_status_cache.h \
|
||||
utils/functional.h \
|
||||
utils/gzip.cc \
|
||||
utils/gzip.h \
|
||||
utils/list_focus.h \
|
||||
utils/lockfile.cc \
|
||||
utils/lockfile.h \
|
||||
@@ -195,6 +197,8 @@ libsub_root_a_SOURCES = \
|
||||
globals.h \
|
||||
option_parser.cc \
|
||||
option_parser.h \
|
||||
setup.cc \
|
||||
setup.h \
|
||||
signal_handler.cc \
|
||||
signal_handler.h
|
||||
|
||||
|
||||
+108
-14
@@ -5,7 +5,6 @@
|
||||
#include <functional>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <rak/string_manip.h>
|
||||
#include <rak/regex.h>
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/throttle.h>
|
||||
@@ -22,6 +21,7 @@
|
||||
#include <torrent/utils/file_stat.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/option_strings.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "core/download.h"
|
||||
#include "core/manager.h"
|
||||
@@ -46,6 +46,8 @@ retrieve_d_base_filename(core::Download* download) {
|
||||
|
||||
if (download->file_list()->is_multi_file())
|
||||
base = &download->file_list()->frozen_root_dir();
|
||||
else if (download->file_list()->empty())
|
||||
return std::string();
|
||||
else
|
||||
base = &download->file_list()->at(0)->frozen_path();
|
||||
|
||||
@@ -84,7 +86,7 @@ apply_d_change_link(core::Download* download, const torrent::Object::list_type&
|
||||
|
||||
// } else if (type == "directory_path") {
|
||||
// target = rpc::call_command_string("d.directory", rpc::make_target(download));
|
||||
// link = rak::path_expand(prefix + rpc::call_command_string("d.base_path", rpc::make_target(download)) + postfix);
|
||||
// link = path_expand(prefix + rpc::call_command_string("d.base_path", rpc::make_target(download)) + postfix);
|
||||
|
||||
} else if (type == "tied") {
|
||||
link = expand_path(rpc::call_command_string("d.tied_to_file", rpc::make_target(download)));
|
||||
@@ -276,7 +278,7 @@ retrieve_d_bitfield(core::Download* download) {
|
||||
if (bitField->empty())
|
||||
return torrent::Object("");
|
||||
|
||||
return torrent::Object(rak::transform_hex(bitField->begin(), bitField->end()));
|
||||
return torrent::Object(torrent::utils::transform_to_hex_str(*bitField));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -326,7 +328,8 @@ d_chunks_seen(core::Download* download) {
|
||||
std::string result;
|
||||
result.resize(size * 2);
|
||||
|
||||
rak::transform_hex((const char*)seen, (const char*)seen + size, result.begin());
|
||||
torrent::utils::transform_to_hex(seen, seen + size, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -444,9 +447,11 @@ p_call_target(const torrent::Object::list_type& args) {
|
||||
|
||||
torrent::HashString hash;
|
||||
|
||||
if (peer_id.size() != 40 ||
|
||||
torrent::hash_string_from_hex_c_str(peer_id.c_str(), hash) == peer_id.c_str())
|
||||
throw torrent::input_error("Not a hash string.");
|
||||
if (peer_id.size() != 40)
|
||||
throw torrent::input_error("invalid argument: peer id target is not 40 bytes long");
|
||||
|
||||
if (torrent::utils::transform_from_hex(peer_id.c_str(), peer_id.c_str() + 40, hash) != hash.end())
|
||||
throw torrent::input_error("invalid argument: peer id target is not a hex string");
|
||||
|
||||
torrent::ConnectionList::iterator peerItr = download->connection_list()->find(hash.c_str());
|
||||
|
||||
@@ -467,12 +472,14 @@ download_tracker_insert(core::Download* download, const torrent::Object::list_ty
|
||||
if (args.size() != 2)
|
||||
throw torrent::input_error("Wrong argument count.");
|
||||
|
||||
int64_t group;
|
||||
int64_t group = 0;
|
||||
|
||||
if (args.front().is_string())
|
||||
rpc::parse_whole_value_nothrow(args.front().as_string().c_str(), &group);
|
||||
else
|
||||
if (args.front().is_string()) {
|
||||
if (!rpc::parse_whole_value_nothrow(args.front().as_string().c_str(), &group))
|
||||
throw torrent::input_error("Invalid tracker group number.");
|
||||
} else {
|
||||
group = args.front().as_value();
|
||||
}
|
||||
|
||||
if (group < 0 || group > 32)
|
||||
throw torrent::input_error("Tracker group number invalid.");
|
||||
@@ -641,9 +648,9 @@ void cg_d_group_set(core::Download* download, const torrent::Objec
|
||||
|
||||
void
|
||||
initialize_command_download() {
|
||||
CMD2_DL("d.hash", std::bind(&rak::transform_hex_str<torrent::HashString>, CMD2_ON_INFO(hash)));
|
||||
CMD2_DL("d.local_id", std::bind(&rak::transform_hex_str<torrent::HashString>, CMD2_ON_INFO(local_id)));
|
||||
CMD2_DL("d.local_id_html", std::bind(&rak::copy_escape_html_str<torrent::HashString>, CMD2_ON_INFO(local_id)));
|
||||
CMD2_DL("d.hash", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(download->info()->hash()); });
|
||||
CMD2_DL("d.local_id", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(download->info()->local_id()); });
|
||||
CMD2_DL("d.local_id_html", [](auto* download, auto) { return torrent::utils::copy_escape_html_str(download->info()->local_id()); });
|
||||
CMD2_DL("d.bitfield", std::bind(&retrieve_d_bitfield, std::placeholders::_1));
|
||||
CMD2_DL("d.base_path", std::bind(&retrieve_d_base_path, std::placeholders::_1));
|
||||
CMD2_DL("d.base_filename", std::bind(&retrieve_d_base_filename, std::placeholders::_1));
|
||||
@@ -884,4 +891,91 @@ initialize_command_download() {
|
||||
CMD2_DL_LIST ("t.multicall", std::bind(&t_multicall, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
CMD2_ANY_LIST ("p.call_target", std::bind(&p_call_target, std::placeholders::_2));
|
||||
|
||||
rpc::rpc.mark_safe("add_peer");
|
||||
rpc::rpc.mark_safe("d.hash");
|
||||
rpc::rpc.mark_safe("d.local_id");
|
||||
rpc::rpc.mark_safe("d.local_id_html");
|
||||
rpc::rpc.mark_safe("d.bitfield");
|
||||
rpc::rpc.mark_safe("d.base_path");
|
||||
rpc::rpc.mark_safe("d.base_filename");
|
||||
rpc::rpc.mark_safe("d.name");
|
||||
rpc::rpc.mark_safe("d.directory");
|
||||
rpc::rpc.mark_safe("d.directory_base");
|
||||
rpc::rpc.mark_safe("d.creation_date");
|
||||
rpc::rpc.mark_safe("d.load_date");
|
||||
rpc::rpc.mark_safe("d.up.rate");
|
||||
rpc::rpc.mark_safe("d.up.total");
|
||||
rpc::rpc.mark_safe("d.down.rate");
|
||||
rpc::rpc.mark_safe("d.down.total");
|
||||
rpc::rpc.mark_safe("d.skip.rate");
|
||||
rpc::rpc.mark_safe("d.skip.total");
|
||||
rpc::rpc.mark_safe("d.is_open");
|
||||
rpc::rpc.mark_safe("d.is_active");
|
||||
rpc::rpc.mark_safe("d.is_hash_checked");
|
||||
rpc::rpc.mark_safe("d.is_hash_checking");
|
||||
rpc::rpc.mark_safe("d.is_multi_file");
|
||||
rpc::rpc.mark_safe("d.is_private");
|
||||
rpc::rpc.mark_safe("d.is_pex_active");
|
||||
rpc::rpc.mark_safe("d.is_partially_done");
|
||||
rpc::rpc.mark_safe("d.is_not_partially_done");
|
||||
rpc::rpc.mark_safe("d.is_meta");
|
||||
rpc::rpc.mark_safe("d.peer_exchange");
|
||||
rpc::rpc.mark_safe("d.resume");
|
||||
rpc::rpc.mark_safe("d.pause");
|
||||
rpc::rpc.mark_safe("d.open");
|
||||
rpc::rpc.mark_safe("d.close");
|
||||
rpc::rpc.mark_safe("d.close.directly");
|
||||
rpc::rpc.mark_safe("d.erase");
|
||||
rpc::rpc.mark_safe("d.check_hash");
|
||||
rpc::rpc.mark_safe("d.save_resume");
|
||||
rpc::rpc.mark_safe("d.save_full_session");
|
||||
rpc::rpc.mark_safe("d.update_priorities");
|
||||
rpc::rpc.mark_safe("d.custom");
|
||||
rpc::rpc.mark_safe("d.custom1");
|
||||
rpc::rpc.mark_safe("d.custom2");
|
||||
rpc::rpc.mark_safe("d.custom3");
|
||||
rpc::rpc.mark_safe("d.custom4");
|
||||
rpc::rpc.mark_safe("d.custom5");
|
||||
rpc::rpc.mark_safe("d.size_bytes");
|
||||
rpc::rpc.mark_safe("d.size_chunks");
|
||||
rpc::rpc.mark_safe("d.size_pex");
|
||||
rpc::rpc.mark_safe("d.completed_bytes");
|
||||
rpc::rpc.mark_safe("d.bytes_done");
|
||||
rpc::rpc.mark_safe("d.peers_accounted");
|
||||
rpc::rpc.mark_safe("d.chunks_hashed");
|
||||
rpc::rpc.mark_safe("d.tracker_size");
|
||||
rpc::rpc.mark_safe("d.completed_chunks");
|
||||
rpc::rpc.mark_safe("d.left_bytes");
|
||||
rpc::rpc.mark_safe("d.chunk_size");
|
||||
rpc::rpc.mark_safe("d.priority");
|
||||
rpc::rpc.mark_safe("d.priority_str");
|
||||
rpc::rpc.mark_safe("d.state");
|
||||
rpc::rpc.mark_safe("d.state_changed");
|
||||
rpc::rpc.mark_safe("d.state_counter");
|
||||
rpc::rpc.mark_safe("d.connection_current");
|
||||
rpc::rpc.mark_safe("d.connection_leech");
|
||||
rpc::rpc.mark_safe("d.connection_seed");
|
||||
rpc::rpc.mark_safe("d.throttle_name");
|
||||
rpc::rpc.mark_safe("d.uploads_max");
|
||||
rpc::rpc.mark_safe("d.downloads_max");
|
||||
rpc::rpc.mark_safe("d.peers_min");
|
||||
rpc::rpc.mark_safe("d.peers_max");
|
||||
rpc::rpc.mark_safe("d.peers_connected");
|
||||
rpc::rpc.mark_safe("d.peers_not_connected");
|
||||
rpc::rpc.mark_safe("d.peers_complete");
|
||||
rpc::rpc.mark_safe("d.tracker_numwant");
|
||||
rpc::rpc.mark_safe("d.tracker_focus");
|
||||
rpc::rpc.mark_safe("d.message");
|
||||
rpc::rpc.mark_safe("d.hashing");
|
||||
rpc::rpc.mark_safe("d.hashing_failed");
|
||||
rpc::rpc.mark_safe("d.free_diskspace");
|
||||
rpc::rpc.mark_safe("d.views");
|
||||
rpc::rpc.mark_safe("d.views.remove");
|
||||
rpc::rpc.mark_safe("d.views.push_back_unique");
|
||||
rpc::rpc.mark_safe("d.ratio");
|
||||
rpc::rpc.mark_safe("f.multicall");
|
||||
rpc::rpc.mark_safe("p.multicall");
|
||||
rpc::rpc.mark_safe("p.call_target");
|
||||
rpc::rpc.mark_safe("t.multicall");
|
||||
}
|
||||
|
||||
@@ -455,4 +455,27 @@ initialize_command_dynamic() {
|
||||
CMD2_ANY ("strings.tracker_event", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_EVENT));
|
||||
CMD2_ANY ("strings.tracker_mode", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_MODE));
|
||||
// clang-format on
|
||||
|
||||
#ifdef HAVE_XMLRPC_TINYXML2
|
||||
rpc::rpc.mark_safe("system.listMethods");
|
||||
#endif
|
||||
|
||||
rpc::rpc.mark_safe("method.use_deprecated");
|
||||
rpc::rpc.mark_safe("method.const");
|
||||
rpc::rpc.mark_safe("method.has_key");
|
||||
rpc::rpc.mark_safe("method.list_keys");
|
||||
rpc::rpc.mark_safe("method.get");
|
||||
rpc::rpc.mark_safe("method.rlookup");
|
||||
rpc::rpc.mark_safe("catch");
|
||||
|
||||
rpc::rpc.mark_safe("strings.choke_heuristics");
|
||||
rpc::rpc.mark_safe("strings.choke_heuristics.upload");
|
||||
rpc::rpc.mark_safe("strings.choke_heuristics.download");
|
||||
rpc::rpc.mark_safe("strings.connection_type");
|
||||
rpc::rpc.mark_safe("strings.encryption");
|
||||
rpc::rpc.mark_safe("strings.ip_filter");
|
||||
rpc::rpc.mark_safe("strings.ip_tos");
|
||||
rpc::rpc.mark_safe("strings.log_group");
|
||||
rpc::rpc.mark_safe("strings.tracker_event");
|
||||
rpc::rpc.mark_safe("strings.tracker_mode");
|
||||
}
|
||||
|
||||
+13
-2
@@ -2,12 +2,12 @@
|
||||
|
||||
#include <functional>
|
||||
#include <cstdio>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/hash_string.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/directory_events.h>
|
||||
#include <torrent/utils/file_stat.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "control.h"
|
||||
@@ -208,7 +208,7 @@ apply_download_list(const torrent::Object::list_type& args) {
|
||||
for (core::View::const_iterator itr = (*view_itr)->begin_visible(), last = (*view_itr)->end_visible(); itr != last; itr++) {
|
||||
const torrent::HashString* hashString = &(*itr)->info()->hash();
|
||||
|
||||
resultList.push_back(rak::transform_hex(hashString->begin(), hashString->end()));
|
||||
resultList.push_back(torrent::utils::transform_to_hex_str(*hashString));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -344,4 +344,15 @@ initialize_command_events() {
|
||||
CMD2_ANY_LIST ("d.multicall.filtered", std::bind(&d_multicall_filtered, std::placeholders::_2));
|
||||
|
||||
CMD2_ANY_LIST ("directory.watch.added", std::bind(&directory_watch_added, std::placeholders::_2));
|
||||
|
||||
rpc::rpc.mark_safe("start_tied");
|
||||
rpc::rpc.mark_safe("stop_untied");
|
||||
rpc::rpc.mark_safe("close_untied");
|
||||
rpc::rpc.mark_safe("remove_untied");
|
||||
|
||||
rpc::rpc.mark_safe("close_low_diskspace");
|
||||
rpc::rpc.mark_safe("close_low_diskspace.normal");
|
||||
rpc::rpc.mark_safe("download_list");
|
||||
rpc::rpc.mark_safe("d.multicall2");
|
||||
rpc::rpc.mark_safe("d.multicall.filtered");
|
||||
}
|
||||
|
||||
@@ -103,4 +103,32 @@ initialize_command_file() {
|
||||
|
||||
CMD2_FILEITR("fi.filename_last", std::bind(&apply_fi_filename_last, std::placeholders::_1));
|
||||
CMD2_FILEITR("fi.is_file", std::bind(&torrent::FileListIterator::is_file, std::placeholders::_1));
|
||||
|
||||
rpc::rpc.mark_safe("f.path");
|
||||
rpc::rpc.mark_safe("f.path_components");
|
||||
rpc::rpc.mark_safe("f.path_depth");
|
||||
rpc::rpc.mark_safe("f.frozen_path");
|
||||
rpc::rpc.mark_safe("f.offset");
|
||||
rpc::rpc.mark_safe("f.size_bytes");
|
||||
rpc::rpc.mark_safe("f.size_chunks");
|
||||
rpc::rpc.mark_safe("f.completed_chunks");
|
||||
rpc::rpc.mark_safe("f.range_first");
|
||||
rpc::rpc.mark_safe("f.range_second");
|
||||
rpc::rpc.mark_safe("f.priority");
|
||||
rpc::rpc.mark_safe("f.priority.set");
|
||||
rpc::rpc.mark_safe("f.is_created");
|
||||
rpc::rpc.mark_safe("f.is_open");
|
||||
rpc::rpc.mark_safe("f.is_create_queued");
|
||||
rpc::rpc.mark_safe("f.is_resize_queued");
|
||||
rpc::rpc.mark_safe("f.prioritize_first");
|
||||
rpc::rpc.mark_safe("f.prioritize_first.enable");
|
||||
rpc::rpc.mark_safe("f.prioritize_first.disable");
|
||||
rpc::rpc.mark_safe("f.prioritize_last");
|
||||
rpc::rpc.mark_safe("f.prioritize_last.enable");
|
||||
rpc::rpc.mark_safe("f.prioritize_last.disable");
|
||||
rpc::rpc.mark_safe("f.last_touched");
|
||||
rpc::rpc.mark_safe("f.match_depth_prev");
|
||||
rpc::rpc.mark_safe("f.match_depth_next");
|
||||
rpc::rpc.mark_safe("fi.filename_last");
|
||||
rpc::rpc.mark_safe("fi.is_file");
|
||||
}
|
||||
|
||||
@@ -385,4 +385,24 @@ initialize_command_groups() {
|
||||
CMD2_ANY ("choke_group.down.heuristics", std::bind(&torrent::option_as_string, torrent::OPTION_CHOKE_HEURISTICS,
|
||||
std::bind(&torrent::choke_queue::heuristics, CHOKE_GROUP(&torrent::choke_group::down_queue))));
|
||||
CMD2_ANY_LIST ("choke_group.down.heuristics.set", std::bind(&apply_cg_heuristics_set, std::placeholders::_2, false));
|
||||
|
||||
rpc::rpc.mark_safe("choke_group.list");
|
||||
rpc::rpc.mark_safe("choke_group.size");
|
||||
rpc::rpc.mark_safe("choke_group.index_of");
|
||||
rpc::rpc.mark_safe("choke_group.general.size");
|
||||
rpc::rpc.mark_safe("choke_group.tracker.mode");
|
||||
rpc::rpc.mark_safe("choke_group.up.rate");
|
||||
rpc::rpc.mark_safe("choke_group.down.rate");
|
||||
rpc::rpc.mark_safe("choke_group.up.max");
|
||||
rpc::rpc.mark_safe("choke_group.up.max.unlimited");
|
||||
rpc::rpc.mark_safe("choke_group.up.total");
|
||||
rpc::rpc.mark_safe("choke_group.up.queued");
|
||||
rpc::rpc.mark_safe("choke_group.up.unchoked");
|
||||
rpc::rpc.mark_safe("choke_group.up.heuristics");
|
||||
rpc::rpc.mark_safe("choke_group.down.max");
|
||||
rpc::rpc.mark_safe("choke_group.down.max.unlimited");
|
||||
rpc::rpc.mark_safe("choke_group.down.total");
|
||||
rpc::rpc.mark_safe("choke_group.down.queued");
|
||||
rpc::rpc.mark_safe("choke_group.down.unchoked");
|
||||
rpc::rpc.mark_safe("choke_group.down.heuristics");
|
||||
}
|
||||
|
||||
+8
-3
@@ -29,7 +29,7 @@ torrent::Object
|
||||
apply_ip_tables_size_data(const std::string& args) {
|
||||
rpc::ip_table_list::const_iterator itr = ip_tables.find(args);
|
||||
|
||||
if (itr != ip_tables.end())
|
||||
if (itr == ip_tables.end())
|
||||
throw torrent::input_error("IP table does not exist.");
|
||||
|
||||
uint32_t size = itr->table.sizeof_data();
|
||||
@@ -203,9 +203,14 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
|
||||
uint32_t mask=0;
|
||||
uint32_t end_mask=0;
|
||||
|
||||
mask = (~mask) << (32-mask_bits);
|
||||
if (mask_bits == 0) {
|
||||
mask = 0;
|
||||
end_mask = ~(uint32_t)0;
|
||||
} else {
|
||||
mask = (~mask) << (32-mask_bits);
|
||||
end_mask = (~end_mask) >> mask_bits;
|
||||
}
|
||||
*address_start = ip & mask;
|
||||
end_mask = (~end_mask) >> mask_bits;
|
||||
*address_end = (ip & mask) | end_mask;
|
||||
|
||||
valid=true;
|
||||
|
||||
+43
-6
@@ -17,7 +17,6 @@
|
||||
#include "core/download.h"
|
||||
#include "core/download_list.h"
|
||||
#include "core/manager.h"
|
||||
#include "rak/string_manip.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "rpc/scgi.h"
|
||||
#include "session/session_manager.h"
|
||||
@@ -95,8 +94,12 @@ post_increment(torrent::Object::list_const_iterator& itr, const torrent::Object:
|
||||
|
||||
inline const std::string&
|
||||
check_name(const std::string& str) {
|
||||
if (!rak::is_all_name(str))
|
||||
throw torrent::input_error("Non-alphanumeric characters found.");
|
||||
auto itr = std::find_if(str.begin(), str.end(), [](char c) {
|
||||
return !std::isalnum(c, std::locale::classic()) && c != '_';
|
||||
});
|
||||
|
||||
if (itr != str.end())
|
||||
throw torrent::input_error("Invalid characters found in name.");
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -120,6 +123,15 @@ group_insert(const torrent::Object::list_type& args) {
|
||||
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.max", "value", (int64_t)300));
|
||||
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.upload", "value", (int64_t)20 << 20));
|
||||
|
||||
rpc::rpc.mark_safe("group." + name + ".view");
|
||||
rpc::rpc.mark_safe("group." + name + ".view.set");
|
||||
rpc::rpc.mark_safe("group." + name + ".ratio.min");
|
||||
rpc::rpc.mark_safe("group." + name + ".ratio.min.set");
|
||||
rpc::rpc.mark_safe("group." + name + ".ratio.max");
|
||||
rpc::rpc.mark_safe("group." + name + ".ratio.max.set");
|
||||
rpc::rpc.mark_safe("group." + name + ".ratio.upload");
|
||||
rpc::rpc.mark_safe("group." + name + ".ratio.upload.set");
|
||||
|
||||
if (rpc::call_command_value("method.use_intermediate") == 3) {
|
||||
// Cleaned up in 0.16.1:
|
||||
|
||||
@@ -173,9 +185,13 @@ cmd_file_append(const torrent::Object::list_type& args) {
|
||||
if (output == nullptr)
|
||||
throw torrent::input_error("Could not append to file '" + args.front().as_string() + "': " + std::strerror(errno));
|
||||
|
||||
file_print_list(++args.begin(), args.end(), output, file_print_delim_space);
|
||||
|
||||
fprintf(output, "\n");
|
||||
try {
|
||||
file_print_list(++args.begin(), args.end(), output, file_print_delim_space);
|
||||
fprintf(output, "\n");
|
||||
} catch (...) {
|
||||
fclose(output);
|
||||
throw;
|
||||
}
|
||||
fclose(output);
|
||||
return torrent::Object();
|
||||
}
|
||||
@@ -319,4 +335,25 @@ initialize_command_local() {
|
||||
CMD2_ANY_P("argument.3", std::bind(&rpc::command_base::argument_ref, 3));
|
||||
|
||||
CMD2_ANY_LIST ("group.insert", std::bind(&group_insert, std::placeholders::_2));
|
||||
|
||||
rpc::rpc.mark_safe("system.api_version");
|
||||
rpc::rpc.mark_safe("system.client_version");
|
||||
rpc::rpc.mark_safe("system.library_version");
|
||||
rpc::rpc.mark_safe("system.file.max_size");
|
||||
rpc::rpc.mark_safe("system.file.split_size");
|
||||
rpc::rpc.mark_safe("system.file.split_suffix");
|
||||
|
||||
rpc::rpc.mark_safe("directory.default");
|
||||
rpc::rpc.mark_safe("session.path");
|
||||
rpc::rpc.mark_safe("session.use_lock");
|
||||
rpc::rpc.mark_safe("session.on_completion");
|
||||
|
||||
rpc::rpc.mark_safe("pieces.sync.always_safe");
|
||||
rpc::rpc.mark_safe("pieces.sync.timeout");
|
||||
rpc::rpc.mark_safe("pieces.sync.timeout_safe");
|
||||
rpc::rpc.mark_safe("pieces.preload.type");
|
||||
rpc::rpc.mark_safe("pieces.preload.min_size");
|
||||
rpc::rpc.mark_safe("pieces.preload.min_rate");
|
||||
rpc::rpc.mark_safe("pieces.memory.max");
|
||||
rpc::rpc.mark_safe("pieces.hash.on_completion");
|
||||
}
|
||||
|
||||
+11
-39
@@ -8,53 +8,23 @@
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/option_strings.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "control.h"
|
||||
#include "command_helpers.h"
|
||||
#include "globals.h"
|
||||
#include "setup.h"
|
||||
#include "core/download.h"
|
||||
#include "core/download_list.h"
|
||||
#include "core/manager.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
static const int log_flag_use_gz = 0x1;
|
||||
static const int log_flag_append_pid = 0x2;
|
||||
static const int log_flag_append_file = 0x4;
|
||||
static const int log_flag_flush = 0x8;
|
||||
|
||||
void
|
||||
log_add_group_output_str(const char* group_name, const char* output_id) {
|
||||
int log_group = torrent::option_find_string(torrent::OPTION_LOG_GROUP, group_name);
|
||||
torrent::log_add_group_output(log_group, output_id);
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
apply_log_open(int output_flags, const torrent::Object::list_type& args) {
|
||||
if (args.size() < 2)
|
||||
throw torrent::input_error("Invalid number of arguments.");
|
||||
apply_log_open(int output_flags, const torrent::Object::list_type& raw_args) {
|
||||
std::vector<std::string> args;
|
||||
|
||||
torrent::Object::list_const_iterator itr = args.begin();
|
||||
|
||||
auto output_id = (itr++)->as_string();
|
||||
auto file_name = expand_path((itr++)->as_string());
|
||||
|
||||
if ((output_flags & log_flag_append_pid)) {
|
||||
char buffer[32];
|
||||
snprintf(buffer, 32, ".%li", (long)getpid());
|
||||
|
||||
file_name += buffer;
|
||||
}
|
||||
|
||||
bool append = (output_flags & log_flag_append_file);
|
||||
bool flush = (output_flags & log_flag_flush);
|
||||
|
||||
if ((output_flags & log_flag_use_gz))
|
||||
torrent::log_open_gz_file_output(output_id.c_str(), file_name.c_str(), append);
|
||||
else
|
||||
torrent::log_open_file_output(output_id.c_str(), file_name.c_str(), append, flush);
|
||||
|
||||
while (itr != args.end())
|
||||
log_add_group_output_str((itr++)->as_string().c_str(), output_id.c_str());
|
||||
for (const auto& arg : raw_args)
|
||||
args.push_back(arg.as_string());
|
||||
|
||||
apply_log_open_str(output_flags, args);
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
@@ -63,8 +33,7 @@ apply_log_add_output(const torrent::Object::list_type& args) {
|
||||
if (args.size() != 2)
|
||||
throw torrent::input_error("Invalid number of arguments.");
|
||||
|
||||
log_add_group_output_str(args.front().as_string().c_str(),
|
||||
args.back().as_string().c_str());
|
||||
log_add_group_output_str(args.front().as_string(), args.back().as_string());
|
||||
|
||||
return torrent::Object();
|
||||
}
|
||||
@@ -118,6 +87,9 @@ log_vmmap_dump(const std::string& str) {
|
||||
|
||||
FILE* log_file = fopen(str.c_str(), "w");
|
||||
|
||||
if (log_file == NULL)
|
||||
throw torrent::input_error("Could not open log file: " + str);
|
||||
|
||||
for (auto& all_mapping : all_mappings) {
|
||||
fprintf(log_file, "%8p-%8p [%5llxk]\n", all_mapping.ptr, (char*)all_mapping.ptr + all_mapping.length, (long long unsigned int)(all_mapping.length / 1024));
|
||||
}
|
||||
|
||||
+109
-18
@@ -3,7 +3,6 @@
|
||||
#include <functional>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <rak/address_info.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/data/file_manager.h>
|
||||
@@ -25,6 +24,11 @@
|
||||
#include "rpc/parse.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
#ifdef HAVE_SYSTEMD
|
||||
#include <sys/socket.h>
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
torrent::Object
|
||||
apply_encryption(const torrent::Object::list_type& args) {
|
||||
uint32_t options_mask = torrent::net::NetworkConfig::encryption_none;
|
||||
@@ -81,14 +85,13 @@ apply_scgi(const std::string& arg, int type) {
|
||||
|
||||
initialize_rpc_handlers();
|
||||
|
||||
rpc::SCgi* scgi = new rpc::SCgi;
|
||||
|
||||
rak::address_info* ai = NULL;
|
||||
torrent::sa_unique_ptr sa;
|
||||
|
||||
auto scgi = std::make_unique<rpc::SCgi>();
|
||||
|
||||
try {
|
||||
int port, err;
|
||||
char dummy;
|
||||
int port{};
|
||||
char dummy{};
|
||||
char address[1024];
|
||||
std::string path;
|
||||
|
||||
@@ -102,10 +105,11 @@ apply_scgi(const std::string& arg, int type) {
|
||||
} else if (std::sscanf(arg.c_str(), "%1023[^:]:%i%c", address, &port, &dummy) == 2 ||
|
||||
std::sscanf(arg.c_str(), "[%64[^]]]:%i%c", address, &port, &dummy) == 2) { // [xx::xx]:port format
|
||||
|
||||
if ((err = rak::address_info::get_address_info(address, PF_UNSPEC, SOCK_STREAM, &ai)) != 0)
|
||||
throw torrent::input_error("Could not bind address: " + std::string(rak::address_info::strerror(err)) + ".");
|
||||
|
||||
sa = torrent::sa_copy(ai->c_addrinfo()->ai_addr);
|
||||
try {
|
||||
sa = torrent::sa_copy(torrent::sa_lookup_address(address, AF_UNSPEC).get());
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Could not bind address: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
lt_log_print(torrent::LOG_RPC_EVENTS, "SCGI socket is bound to an address and might be a security risk");
|
||||
|
||||
@@ -130,17 +134,66 @@ apply_scgi(const std::string& arg, int type) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ai != NULL) rak::address_info::free_address_info(ai);
|
||||
|
||||
} catch (torrent::local_error& e) {
|
||||
if (ai != NULL) rak::address_info::free_address_info(ai);
|
||||
|
||||
delete scgi;
|
||||
throw torrent::input_error(e.what());
|
||||
}
|
||||
|
||||
scgi_thread::set_scgi(scgi.release());
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
apply_scgi_systemd() {
|
||||
#ifdef HAVE_SYSTEMD
|
||||
if (scgi_thread::scgi() != nullptr)
|
||||
throw torrent::input_error("SCGI already enabled.");
|
||||
|
||||
int n = sd_listen_fds(0);
|
||||
if (n < 1)
|
||||
throw torrent::input_error("No systemd socket(s) provided (sd_listen_fds returned " +
|
||||
std::to_string(n) + ").");
|
||||
|
||||
// Iterate over all provided fds. Use the first listening stream socket;
|
||||
// close the rest. The systemd docs say unused fds should be closed.
|
||||
int selected_fd = -1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int fd = SD_LISTEN_FDS_START + i;
|
||||
|
||||
if (selected_fd != -1) {
|
||||
::close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto err = sd_is_socket(fd, AF_UNSPEC, SOCK_STREAM, 1);
|
||||
|
||||
if (err < 0) {
|
||||
// Safe to ignore errors here - we just skip it and move on.
|
||||
::close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
// Not the socket we're looking for.
|
||||
::close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
selected_fd = fd;
|
||||
}
|
||||
|
||||
if (selected_fd == -1)
|
||||
throw torrent::input_error("No listening stream socket found among systemd-provided fds.");
|
||||
|
||||
initialize_rpc_handlers();
|
||||
|
||||
rpc::SCgi* scgi = new rpc::SCgi;
|
||||
scgi->open_fd(selected_fd);
|
||||
|
||||
scgi_thread::set_scgi(scgi);
|
||||
return torrent::Object();
|
||||
#else
|
||||
throw torrent::input_error("Systemd SCGI endpoint is not supported.");
|
||||
#endif
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
@@ -243,10 +296,16 @@ initialize_command_network() {
|
||||
CMD2_ANY_STRING ("network.scgi.open_port", std::bind(&apply_scgi, std::placeholders::_2, 1));
|
||||
CMD2_ANY_STRING ("network.scgi.open_local", std::bind(&apply_scgi, std::placeholders::_2, 2));
|
||||
CMD2_VAR_BOOL ("network.scgi.dont_route", false);
|
||||
CMD2_ANY ("network.scgi.open_systemd", [](auto, auto) { return apply_scgi_systemd(); });
|
||||
|
||||
CMD2_ANY_STRING ("network.xmlrpc.dialect.set", [](const auto&, const auto& arg) { return apply_xmlrpc_dialect(arg); })
|
||||
CMD2_ANY ("network.xmlrpc.size_limit", [](const auto&, const auto&) { return rpc::rpc.size_limit(); });
|
||||
CMD2_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](const auto&, const auto& arg) { return rpc::rpc.set_size_limit(arg); });
|
||||
CMD2_ANY ("network.scgi.use_gzip", [](const auto&, const auto&) { return rpc::rpc.scgi_allow_compression(); });
|
||||
CMD2_ANY_VALUE_V ("network.scgi.use_gzip.set", [](const auto&, const auto& arg) { return rpc::rpc.set_scgi_allow_compression(arg); });
|
||||
CMD2_ANY ("network.scgi.gzip.min_size", [](const auto&, const auto&) { return rpc::rpc.scgi_min_compress_size(); });
|
||||
CMD2_ANY_VALUE_V ("network.scgi.gzip.min_size.set", [](const auto&, const auto& arg) { return rpc::rpc.set_scgi_min_compress_size(arg); });
|
||||
|
||||
CMD2_ANY_STRING ("network.xmlrpc.dialect.set", [](const auto&, const auto& arg) { return apply_xmlrpc_dialect(arg); })
|
||||
CMD2_ANY ("network.xmlrpc.size_limit", [](const auto&, const auto&) { return rpc::rpc.size_limit(); });
|
||||
CMD2_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](const auto&, const auto& arg) { return rpc::rpc.set_size_limit(arg); });
|
||||
|
||||
CMD2_VAR_BOOL ("network.rpc.use_xmlrpc", true);
|
||||
CMD2_VAR_BOOL ("network.rpc.use_jsonrpc", true);
|
||||
@@ -261,4 +320,36 @@ initialize_command_network() {
|
||||
CMD2_ANY_VALUE_V ("network.block.outgoing.set", [nw_config](auto, auto& value) { return nw_config->set_block_outgoing(value); });
|
||||
CMD2_ANY ("network.prefer.ipv6", [nw_config](auto, auto) { return nw_config->is_prefer_ipv6(); });
|
||||
CMD2_ANY_VALUE_V ("network.prefer.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_prefer_ipv6(value); });
|
||||
|
||||
rpc::rpc.mark_safe("network.port_open");
|
||||
rpc::rpc.mark_safe("network.port_random");
|
||||
rpc::rpc.mark_safe("network.port_range");
|
||||
rpc::rpc.mark_safe("network.listen.port");
|
||||
rpc::rpc.mark_safe("network.listen.backlog");
|
||||
|
||||
rpc::rpc.mark_safe("network.http.current_open");
|
||||
rpc::rpc.mark_safe("network.http.max_cache_connections");
|
||||
rpc::rpc.mark_safe("network.http.max_host_connections");
|
||||
rpc::rpc.mark_safe("network.http.max_total_connections");
|
||||
|
||||
rpc::rpc.mark_safe("network.open_files");
|
||||
rpc::rpc.mark_safe("network.max_open_files");
|
||||
rpc::rpc.mark_safe("network.max_open_sockets");
|
||||
rpc::rpc.mark_safe("network.total_handshakes");
|
||||
|
||||
rpc::rpc.mark_safe("network.send_buffer.size");
|
||||
rpc::rpc.mark_safe("network.receive_buffer.size");
|
||||
rpc::rpc.mark_safe("network.bind_address");
|
||||
rpc::rpc.mark_safe("network.local_address");
|
||||
rpc::rpc.mark_safe("network.xmlrpc.size_limit");
|
||||
rpc::rpc.mark_safe("network.open_sockets");
|
||||
rpc::rpc.mark_safe("network.http.cacert");
|
||||
rpc::rpc.mark_safe("network.http.capath");
|
||||
rpc::rpc.mark_safe("network.http.proxy_address");
|
||||
rpc::rpc.mark_safe("network.proxy_address");
|
||||
rpc::rpc.mark_safe("network.scgi.dont_route");
|
||||
rpc::rpc.mark_safe("protocol.pex");
|
||||
|
||||
rpc::rpc.mark_safe("network.rpc.use_xmlrpc");
|
||||
rpc::rpc.mark_safe("network.rpc.use_jsonrpc");
|
||||
}
|
||||
|
||||
+36
-28
@@ -1,33 +1,18 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/bitfield.h>
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/net/socket_address.h>
|
||||
#include <torrent/peer/connection_list.h>
|
||||
#include <torrent/peer/peer.h>
|
||||
#include <torrent/peer/peer_info.h>
|
||||
|
||||
#include "core/manager.h"
|
||||
#include "display/utils.h"
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "control.h"
|
||||
#include "command_helpers.h"
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_id(torrent::Peer* peer) {
|
||||
const torrent::HashString* hashString = &peer->id();
|
||||
|
||||
return rak::transform_hex(hashString->begin(), hashString->end());
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_id_html(torrent::Peer* peer) {
|
||||
const torrent::HashString* hashString = &peer->id();
|
||||
|
||||
return rak::copy_escape_html(hashString->begin(), hashString->end());
|
||||
}
|
||||
#include "control.h"
|
||||
#include "core/manager.h"
|
||||
#include "display/utils.h"
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_address(torrent::Peer* peer) {
|
||||
@@ -40,11 +25,6 @@ retrieve_p_address(torrent::Peer* peer) {
|
||||
return addr_str;
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_port(torrent::Peer* peer) {
|
||||
return torrent::sa_port(peer->peer_info()->socket_address());
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_client_version(torrent::Peer* peer) {
|
||||
char buf[128];
|
||||
@@ -55,18 +35,20 @@ retrieve_p_client_version(torrent::Peer* peer) {
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_options_str(torrent::Peer* peer) {
|
||||
return rak::transform_hex(peer->peer_info()->options(), peer->peer_info()->options() + 8);
|
||||
return torrent::utils::transform_to_hex_str(peer->peer_info()->options(), peer->peer_info()->options() + 8);
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
retrieve_p_completed_percent(torrent::Peer* peer) {
|
||||
if (peer->bitfield()->size_bits() == 0)
|
||||
return int64_t(0);
|
||||
return (100 * peer->bitfield()->size_set()) / peer->bitfield()->size_bits();
|
||||
}
|
||||
|
||||
void
|
||||
initialize_command_peer() {
|
||||
CMD2_PEER("p.id", std::bind(&retrieve_p_id, std::placeholders::_1));
|
||||
CMD2_PEER("p.id_html", std::bind(&retrieve_p_id_html, std::placeholders::_1));
|
||||
CMD2_PEER("p.id", [](auto* peer, auto) { return torrent::utils::transform_to_hex_str(peer->id()); });
|
||||
CMD2_PEER("p.id_html", [](auto* peer, auto) { return torrent::utils::copy_escape_html_str(peer->id()); });
|
||||
CMD2_PEER("p.client_version", std::bind(&retrieve_p_client_version, std::placeholders::_1));
|
||||
|
||||
CMD2_PEER("p.options_str", std::bind(&retrieve_p_options_str, std::placeholders::_1));
|
||||
@@ -80,7 +62,7 @@ initialize_command_peer() {
|
||||
CMD2_PEER("p.is_preferred", std::bind(&torrent::PeerInfo::is_preferred, std::bind(&torrent::Peer::peer_info, std::placeholders::_1)));
|
||||
|
||||
CMD2_PEER("p.address", std::bind(&retrieve_p_address, std::placeholders::_1));
|
||||
CMD2_PEER("p.port", std::bind(&retrieve_p_port, std::placeholders::_1));
|
||||
CMD2_PEER("p.port", [](auto* peer, auto) { return torrent::sa_port(peer->peer_info()->socket_address()); });
|
||||
|
||||
CMD2_PEER("p.completed_percent", std::bind(&retrieve_p_completed_percent, std::placeholders::_1));
|
||||
|
||||
@@ -98,4 +80,30 @@ initialize_command_peer() {
|
||||
|
||||
CMD2_PEER_V("p.disconnect", std::bind(&torrent::Peer::disconnect, std::placeholders::_1, 0));
|
||||
CMD2_PEER_V("p.disconnect_delayed", std::bind(&torrent::Peer::disconnect, std::placeholders::_1, torrent::ConnectionList::disconnect_delayed));
|
||||
|
||||
rpc::rpc.mark_safe("p.address");
|
||||
rpc::rpc.mark_safe("p.port");
|
||||
rpc::rpc.mark_safe("p.client_version");
|
||||
rpc::rpc.mark_safe("p.options_str");
|
||||
rpc::rpc.mark_safe("p.id");
|
||||
rpc::rpc.mark_safe("p.id_html");
|
||||
rpc::rpc.mark_safe("p.up_rate");
|
||||
rpc::rpc.mark_safe("p.up_total");
|
||||
rpc::rpc.mark_safe("p.down_rate");
|
||||
rpc::rpc.mark_safe("p.down_total");
|
||||
rpc::rpc.mark_safe("p.peer_rate");
|
||||
rpc::rpc.mark_safe("p.peer_total");
|
||||
rpc::rpc.mark_safe("p.is_encrypted");
|
||||
rpc::rpc.mark_safe("p.is_incoming");
|
||||
rpc::rpc.mark_safe("p.is_obfuscated");
|
||||
rpc::rpc.mark_safe("p.is_snubbed");
|
||||
rpc::rpc.mark_safe("p.is_unwanted");
|
||||
rpc::rpc.mark_safe("p.is_preferred");
|
||||
rpc::rpc.mark_safe("p.snubbed");
|
||||
rpc::rpc.mark_safe("p.snubbed.set");
|
||||
rpc::rpc.mark_safe("p.banned");
|
||||
rpc::rpc.mark_safe("p.banned.set");
|
||||
rpc::rpc.mark_safe("p.completed_percent");
|
||||
rpc::rpc.mark_safe("p.disconnect");
|
||||
rpc::rpc.mark_safe("p.disconnect_delayed");
|
||||
}
|
||||
|
||||
+73
-12
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "core/manager.h"
|
||||
#include "ui/root.h"
|
||||
#include "rak/address_info.h"
|
||||
#include "rpc/parse.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
@@ -21,21 +20,26 @@ parse_address_range(const torrent::Object::list_type& args, torrent::Object::lis
|
||||
unsigned int prefixWidth, ret;
|
||||
char dummy;
|
||||
char host[1024];
|
||||
rak::address_info* ai;
|
||||
torrent::sa_unique_ptr sa;
|
||||
|
||||
ret = std::sscanf(itr->as_string().c_str(), "%1023[^/]/%d%c", host, &prefixWidth, &dummy);
|
||||
|
||||
if (ret < 1 || rak::address_info::get_address_info(host, PF_INET, SOCK_STREAM, &ai) != 0)
|
||||
throw torrent::input_error("Could not resolve host.");
|
||||
if (ret < 1)
|
||||
throw torrent::input_error("Invalid address/prefix.");
|
||||
|
||||
try {
|
||||
sa = torrent::sa_copy(torrent::sa_lookup_address(host, AF_INET).get());
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
uint32_t begin, end;
|
||||
auto sa = torrent::sa_copy(ai->c_addrinfo()->ai_addr);
|
||||
|
||||
auto sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
|
||||
|
||||
begin = end = sa_addr;
|
||||
|
||||
rak::address_info::free_address_info(ai);
|
||||
|
||||
if (ret == 2) {
|
||||
if (++itr != args.end())
|
||||
throw torrent::input_error("Cannot specify both network and range end.");
|
||||
@@ -48,13 +52,14 @@ parse_address_range(const torrent::Object::list_type& args, torrent::Object::lis
|
||||
end = sa_addr | ~netmask;
|
||||
|
||||
} else if (++itr != args.end()) {
|
||||
if (rak::address_info::get_address_info(itr->as_string().c_str(), PF_INET, SOCK_STREAM, &ai) != 0)
|
||||
throw torrent::input_error("Could not resolve host.");
|
||||
try {
|
||||
sa = torrent::sa_copy(torrent::sa_lookup_address(itr->as_string(), AF_INET).get());
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
sa = torrent::sa_copy(ai->c_addrinfo()->ai_addr);
|
||||
sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
|
||||
|
||||
rak::address_info::free_address_info(ai);
|
||||
end = sa_addr;
|
||||
}
|
||||
|
||||
@@ -199,4 +204,60 @@ initialize_command_throttle() {
|
||||
CMD2_ANY_STRING ("throttle.up.rate", std::bind(&retrieve_throttle_info, std::placeholders::_2, throttle_info_up | throttle_info_rate));
|
||||
CMD2_ANY_STRING ("throttle.down.max", std::bind(&retrieve_throttle_info, std::placeholders::_2, throttle_info_down | throttle_info_max));
|
||||
CMD2_ANY_STRING ("throttle.down.rate", std::bind(&retrieve_throttle_info, std::placeholders::_2, throttle_info_down | throttle_info_rate));
|
||||
|
||||
rpc::rpc.mark_safe("throttle.unchoked_uploads");
|
||||
rpc::rpc.mark_safe("throttle.max_unchoked_uploads");
|
||||
rpc::rpc.mark_safe("throttle.unchoked_downloads");
|
||||
rpc::rpc.mark_safe("throttle.max_unchoked_downloads");
|
||||
|
||||
rpc::rpc.mark_safe("throttle.min_peers.normal");
|
||||
rpc::rpc.mark_safe("throttle.min_peers.normal.set");
|
||||
rpc::rpc.mark_safe("throttle.max_peers.normal");
|
||||
rpc::rpc.mark_safe("throttle.max_peers.normal.set");
|
||||
rpc::rpc.mark_safe("throttle.min_peers.seed");
|
||||
rpc::rpc.mark_safe("throttle.min_peers.seed.set");
|
||||
rpc::rpc.mark_safe("throttle.max_peers.seed");
|
||||
rpc::rpc.mark_safe("throttle.max_peers.seed.set");
|
||||
|
||||
rpc::rpc.mark_safe("throttle.min_uploads");
|
||||
rpc::rpc.mark_safe("throttle.min_uploads.set");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.set");
|
||||
rpc::rpc.mark_safe("throttle.min_downloads");
|
||||
rpc::rpc.mark_safe("throttle.min_downloads.set");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.set");
|
||||
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.div");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.div.set");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.div._val");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.div._val.set");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.global");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.global.set");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.global._val");
|
||||
rpc::rpc.mark_safe("throttle.max_uploads.global._val.set");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.div");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.div.set");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.div._val");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.div._val.set");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.global");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.global.set");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.global._val");
|
||||
rpc::rpc.mark_safe("throttle.max_downloads.global._val.set");
|
||||
|
||||
rpc::rpc.mark_safe("throttle.global_up.rate");
|
||||
rpc::rpc.mark_safe("throttle.global_up.total");
|
||||
rpc::rpc.mark_safe("throttle.global_up.max_rate");
|
||||
rpc::rpc.mark_safe("throttle.global_up.max_rate.set");
|
||||
rpc::rpc.mark_safe("throttle.global_up.max_rate.set_kb");
|
||||
rpc::rpc.mark_safe("throttle.global_down.rate");
|
||||
rpc::rpc.mark_safe("throttle.global_down.total");
|
||||
rpc::rpc.mark_safe("throttle.global_down.max_rate");
|
||||
rpc::rpc.mark_safe("throttle.global_down.max_rate.set");
|
||||
rpc::rpc.mark_safe("throttle.global_down.max_rate.set_kb");
|
||||
|
||||
rpc::rpc.mark_safe("throttle.up.max");
|
||||
rpc::rpc.mark_safe("throttle.up.rate");
|
||||
rpc::rpc.mark_safe("throttle.down.max");
|
||||
rpc::rpc.mark_safe("throttle.down.rate");
|
||||
}
|
||||
|
||||
@@ -151,4 +151,35 @@ initialize_command_tracker() {
|
||||
|
||||
CMD2_ANY_STRING ("dht.add_node", std::bind(&apply_dht_add_node, std::placeholders::_2));
|
||||
CMD2_ANY ("dht.statistics", std::bind(&core::DhtManager::dht_statistics, dht_manager));
|
||||
|
||||
rpc::rpc.mark_safe("t.url");
|
||||
rpc::rpc.mark_safe("t.group");
|
||||
rpc::rpc.mark_safe("t.id");
|
||||
rpc::rpc.mark_safe("t.type");
|
||||
rpc::rpc.mark_safe("t.is_usable");
|
||||
rpc::rpc.mark_safe("t.is_busy");
|
||||
rpc::rpc.mark_safe("t.is_enabled");
|
||||
rpc::rpc.mark_safe("t.is_enabled.set");
|
||||
rpc::rpc.mark_safe("t.is_extra_tracker");
|
||||
rpc::rpc.mark_safe("t.is_open");
|
||||
rpc::rpc.mark_safe("t.normal_interval");
|
||||
rpc::rpc.mark_safe("t.scrape_time_last");
|
||||
rpc::rpc.mark_safe("t.scrape_counter");
|
||||
rpc::rpc.mark_safe("t.success_time_last");
|
||||
rpc::rpc.mark_safe("t.success_counter");
|
||||
rpc::rpc.mark_safe("t.failed_time_last");
|
||||
rpc::rpc.mark_safe("t.failed_counter");
|
||||
rpc::rpc.mark_safe("t.activity_time_last");
|
||||
rpc::rpc.mark_safe("t.activity_time_next");
|
||||
rpc::rpc.mark_safe("t.scrape_complete");
|
||||
rpc::rpc.mark_safe("t.scrape_incomplete");
|
||||
rpc::rpc.mark_safe("t.scrape_downloaded");
|
||||
|
||||
rpc::rpc.mark_safe("dht.mode.set");
|
||||
rpc::rpc.mark_safe("dht.port");
|
||||
rpc::rpc.mark_safe("dht.override_port");
|
||||
rpc::rpc.mark_safe("dht.add_node");
|
||||
rpc::rpc.mark_safe("dht.statistics");
|
||||
rpc::rpc.mark_safe("trackers.numwant");
|
||||
rpc::rpc.mark_safe("trackers.use_udp");
|
||||
}
|
||||
|
||||
+46
-2
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <ctime>
|
||||
#include <regex>
|
||||
#include <rak/algorithm.h>
|
||||
#include <torrent/utils/log.h>
|
||||
|
||||
#include "core/manager.h"
|
||||
@@ -730,6 +729,26 @@ apply_arith_count(const torrent::Object::list_type& args) {
|
||||
return val;
|
||||
}
|
||||
|
||||
// Get the median of an unordered set of numbers of arbitrary
|
||||
// type by modifing the underlying dataset
|
||||
template <typename T = double, typename _InputIter>
|
||||
T median(_InputIter __first, _InputIter __last) {
|
||||
T __med;
|
||||
|
||||
unsigned int __size = __last - __first;
|
||||
unsigned int __middle = __size / 2;
|
||||
_InputIter __target1 = __first + __middle;
|
||||
std::nth_element(__first, __target1, __last);
|
||||
__med = *__target1;
|
||||
|
||||
if (__size % 2 == 0) {
|
||||
_InputIter __target2 = std::max_element(__first, __target1);
|
||||
__med = (__med + *__target2) / 2.0;
|
||||
}
|
||||
|
||||
return __med;
|
||||
}
|
||||
|
||||
int64_t
|
||||
apply_arith_other(const char* op, const torrent::Object::list_type& args) {
|
||||
if (args.size() == 0)
|
||||
@@ -739,7 +758,8 @@ apply_arith_other(const char* op, const torrent::Object::list_type& args) {
|
||||
return (int64_t)(apply_math_basic(op, std::plus<int64_t>(), args) / apply_arith_count(args));
|
||||
} else if (strcmp(op, "median") == 0) {
|
||||
std::vector<int64_t> result = as_vector(args);
|
||||
return (int64_t)rak::median(result.begin(), result.end());
|
||||
|
||||
return (int64_t)median(result.begin(), result.end());
|
||||
} else {
|
||||
throw torrent::input_error("Wrong operation supplied to apply_arith_other.");
|
||||
}
|
||||
@@ -888,4 +908,28 @@ initialize_command_ui() {
|
||||
return control->object_storage()->get_str(display::color_vars[color_id]);
|
||||
});
|
||||
}
|
||||
|
||||
rpc::rpc.mark_safe("view.set_visible");
|
||||
rpc::rpc.mark_safe("view.set_not_visible");
|
||||
|
||||
rpc::rpc.mark_safe("cat");
|
||||
rpc::rpc.mark_safe("if");
|
||||
rpc::rpc.mark_safe("branch");
|
||||
rpc::rpc.mark_safe("and");
|
||||
rpc::rpc.mark_safe("or");
|
||||
rpc::rpc.mark_safe("not");
|
||||
rpc::rpc.mark_safe("value");
|
||||
rpc::rpc.mark_safe("compare");
|
||||
rpc::rpc.mark_safe("elapsed.less");
|
||||
rpc::rpc.mark_safe("elapsed.greater");
|
||||
|
||||
rpc::rpc.mark_safe("convert.gm_time");
|
||||
rpc::rpc.mark_safe("convert.gm_date");
|
||||
rpc::rpc.mark_safe("convert.time");
|
||||
rpc::rpc.mark_safe("convert.date");
|
||||
rpc::rpc.mark_safe("convert.elapsed_time");
|
||||
rpc::rpc.mark_safe("convert.kb");
|
||||
rpc::rpc.mark_safe("convert.mb");
|
||||
rpc::rpc.mark_safe("convert.xb");
|
||||
rpc::rpc.mark_safe("convert.throttle");
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ Download::set_root_directory(const std::string& path) {
|
||||
rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(this));
|
||||
control->core()->download_list()->close_directly(this);
|
||||
|
||||
throw torrent::input_error("Cannot change the directory of an open download atter the files have been moved.");
|
||||
throw torrent::input_error("Cannot change the directory of an open download after the files have been moved.");
|
||||
}
|
||||
|
||||
control->core()->download_list()->close_directly(this);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/data/file_utils.h>
|
||||
#include <torrent/net/http_stack.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
@@ -327,7 +328,7 @@ void
|
||||
DownloadFactory::log_created(Download* download, torrent::Object* rtorrent) {
|
||||
std::stringstream dump;
|
||||
|
||||
dump << "info_hash = " << torrent::hash_string_to_hex_str(download->info()->hash()) << std::endl;
|
||||
dump << "info_hash = " << torrent::utils::transform_to_hex_str(download->info()->hash()) << std::endl;
|
||||
dump << "session = " << (m_session ? "true" : "false") << std::endl;
|
||||
|
||||
if (download->download()->info()->is_meta_download())
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/data/file.h>
|
||||
#include <torrent/utils/resume.h>
|
||||
#include <torrent/exceptions.h>
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <torrent/object_stream.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
@@ -82,10 +83,13 @@ DownloadList::find(const torrent::HashString& hash) {
|
||||
|
||||
DownloadList::iterator
|
||||
DownloadList::find_hex(const char* hash) {
|
||||
if (strlen(hash) < 40)
|
||||
return end();
|
||||
|
||||
torrent::HashString key;
|
||||
|
||||
for (torrent::HashString::iterator itr = key.begin(), last = key.end(); itr != last; itr++, hash += 2)
|
||||
*itr = (rak::hexchar_to_value(*hash) << 4) + rak::hexchar_to_value(*(hash + 1));
|
||||
if (torrent::utils::transform_from_hex(hash, hash + 40, key) != key.end())
|
||||
return end();
|
||||
|
||||
return std::find_if(begin(), end(), [key](Download* d) { return key == d->info()->hash(); });
|
||||
}
|
||||
|
||||
+73
-19
@@ -8,9 +8,7 @@
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <rak/address_info.h>
|
||||
#include <rak/regex.h>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/utils/resume.h>
|
||||
#include <torrent/object.h>
|
||||
#include <torrent/connection_manager.h>
|
||||
@@ -23,6 +21,7 @@
|
||||
#include <torrent/net/socket_address.h>
|
||||
#include <torrent/runtime/network_manager.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "utils/directory.h"
|
||||
@@ -199,7 +198,7 @@ Manager::listen_open() {
|
||||
void
|
||||
Manager::set_proxy_address(const std::string& addr) {
|
||||
int port;
|
||||
rak::address_info* ai;
|
||||
torrent::sa_unique_ptr sa;
|
||||
|
||||
std::string buf(addr.length() + 1, '\0');
|
||||
|
||||
@@ -211,20 +210,18 @@ Manager::set_proxy_address(const std::string& addr) {
|
||||
if (err == 1)
|
||||
port = 80;
|
||||
|
||||
if ((err = rak::address_info::get_address_info(buf.data(), PF_INET, SOCK_STREAM, &ai)) != 0)
|
||||
throw torrent::input_error("Could not set proxy address: " + std::string(rak::address_info::strerror(err)) + ".");
|
||||
|
||||
try {
|
||||
|
||||
auto sa = torrent::sa_copy(ai->c_addrinfo()->ai_addr);
|
||||
torrent::sap_set_port(sa, port);
|
||||
|
||||
torrent::config::network_config()->set_proxy_address(sa.get());
|
||||
|
||||
rak::address_info::free_address_info(ai);
|
||||
sa = torrent::sa_copy(torrent::sa_lookup_address(buf, AF_INET).get());
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Could not resolve proxy address: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
try {
|
||||
torrent::sa_set_port(sa.get(), port);
|
||||
torrent::config::network_config()->set_proxy_address(sa.get());
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
rak::address_info::free_address_info(ai);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -241,9 +238,10 @@ is_data_uri(const std::string& uri) {
|
||||
|
||||
std::string
|
||||
decode_data_uri(const std::string& uri) {
|
||||
const auto start = uri.find("base64,", 5) + 7;
|
||||
if (start == std::string::npos)
|
||||
const auto pos = uri.find("base64,", 5);
|
||||
if (pos == std::string::npos)
|
||||
throw torrent::input_error("Invalid data uri: not base64 encoded.");
|
||||
const auto start = pos + 7;
|
||||
if (start >= uri.size())
|
||||
throw torrent::input_error("Empty base64.");
|
||||
return utils::decode_base64(uri.substr(start));
|
||||
@@ -314,14 +312,70 @@ path_expand_transform(std::string path, const utils::directory_entry& entry) {
|
||||
return path + entry.s_name;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Consider rewritting such that m_seq is replaced by first/last.
|
||||
template <typename Sequence>
|
||||
class split_iterator_t {
|
||||
public:
|
||||
typedef typename Sequence::const_iterator const_iterator;
|
||||
typedef typename Sequence::value_type value_type;
|
||||
|
||||
split_iterator_t() {}
|
||||
|
||||
split_iterator_t(const Sequence& seq, value_type delim) :
|
||||
m_seq(&seq),
|
||||
m_delim(delim),
|
||||
m_pos(seq.begin()),
|
||||
m_next(std::find(seq.begin(), seq.end(), delim)) {
|
||||
}
|
||||
|
||||
Sequence operator * () { return Sequence(m_pos, m_next); }
|
||||
|
||||
split_iterator_t& operator ++ () {
|
||||
m_pos = m_next;
|
||||
|
||||
if (m_pos == m_seq->end())
|
||||
return *this;
|
||||
|
||||
m_pos++;
|
||||
m_next = std::find(m_pos, m_seq->end(), m_delim);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator == (const split_iterator_t&) const { return m_pos == m_seq->end(); }
|
||||
bool operator != (const split_iterator_t&) const { return m_pos != m_seq->end(); }
|
||||
|
||||
private:
|
||||
const Sequence* m_seq;
|
||||
value_type m_delim;
|
||||
const_iterator m_pos;
|
||||
const_iterator m_next;
|
||||
};
|
||||
|
||||
template <typename Sequence>
|
||||
inline split_iterator_t<Sequence>
|
||||
split_iterator(const Sequence& seq, typename Sequence::value_type delim) {
|
||||
return split_iterator_t<Sequence>(seq, delim);
|
||||
}
|
||||
|
||||
template <typename Sequence>
|
||||
inline split_iterator_t<Sequence>
|
||||
split_iterator(const Sequence&) {
|
||||
return split_iterator_t<Sequence>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Move this somewhere better.
|
||||
void
|
||||
path_expand(std::vector<std::string>* paths, const std::string& pattern) {
|
||||
std::vector<utils::Directory> currentCache;
|
||||
std::vector<utils::Directory> nextCache;
|
||||
|
||||
rak::split_iterator_t<std::string> first = rak::split_iterator(pattern, '/');
|
||||
rak::split_iterator_t<std::string> last = rak::split_iterator(pattern);
|
||||
auto first = split_iterator(pattern, '/');
|
||||
auto last = split_iterator(pattern);
|
||||
|
||||
if (first == last)
|
||||
return;
|
||||
@@ -330,7 +384,7 @@ path_expand(std::vector<std::string>* paths, const std::string& pattern) {
|
||||
if ((*first).empty()) {
|
||||
currentCache.push_back(utils::Directory("/"));
|
||||
++first;
|
||||
} else if (rak::trim(*first) == "~") {
|
||||
} else if (torrent::utils::trim_spaces_str(*first) == "~") {
|
||||
currentCache.push_back(utils::Directory("~"));
|
||||
++first;
|
||||
} else {
|
||||
|
||||
@@ -35,7 +35,8 @@ ViewManager::insert(const std::string& name) {
|
||||
View* view = new View();
|
||||
view->initialize(name);
|
||||
|
||||
return base_type::insert(end(), view);
|
||||
base_type::push_back(view);
|
||||
return --end();
|
||||
}
|
||||
|
||||
ViewManager::iterator
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
#define RTORRENT_CORE_VIEW_MANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include <rak/unordered_vector.h>
|
||||
#include <torrent/utils/unordered_vector.h>
|
||||
|
||||
#include "view.h"
|
||||
|
||||
namespace core {
|
||||
|
||||
class ViewManager : public rak::unordered_vector<View*> {
|
||||
class ViewManager : public torrent::utils::unordered_vector<View*> {
|
||||
public:
|
||||
typedef rak::unordered_vector<View*> base_type;
|
||||
typedef std::list<std::string> filter_args;
|
||||
typedef torrent::utils::unordered_vector<View*> base_type;
|
||||
typedef std::list<std::string> filter_args;
|
||||
|
||||
using base_type::iterator;
|
||||
using base_type::const_iterator;
|
||||
|
||||
@@ -361,7 +361,7 @@ Frame::balance_row(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
||||
(*itr)->balance(x, y, m_width, std::min((*itr)->m_height, height));
|
||||
|
||||
y += (*itr)->m_height;
|
||||
height -= (*itr)->m_height;
|
||||
height -= std::min(height, (*itr)->m_height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ Frame::balance_column(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
||||
(*itr)->balance(x, y, std::min((*itr)->m_width, width), m_height);
|
||||
|
||||
x += (*itr)->m_width;
|
||||
width -= (*itr)->m_width;
|
||||
width -= std::min(width, (*itr)->m_width);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
// 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <rak/string_manip.h>
|
||||
#include "display/text_element_string.h"
|
||||
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "text_element_string.h"
|
||||
|
||||
namespace display {
|
||||
|
||||
@@ -53,17 +20,17 @@ TextElementStringBase::print(char* first, char* last, Canvas::attributes_list* a
|
||||
auto buffer = std::make_unique<char[]>(last - first);
|
||||
char* bufferLast = copy_string(buffer.get(), buffer.get() + (last - first), target);
|
||||
|
||||
first = rak::transform_hex(buffer.get(), bufferLast, first, last);
|
||||
first = torrent::utils::transform_to_hex(buffer.get(), bufferLast, first, last);
|
||||
|
||||
} else if (m_flags & flag_escape_html) {
|
||||
auto buffer = std::make_unique<char[]>(last - first);
|
||||
char* bufferLast = copy_string(buffer.get(), buffer.get() + (last - first), target);
|
||||
|
||||
first = rak::copy_escape_html(buffer.get(), bufferLast, first, last);
|
||||
first = torrent::utils::copy_escape_html(buffer.get(), bufferLast, first, last);
|
||||
|
||||
} else {
|
||||
first = copy_string(first, last, target);
|
||||
}
|
||||
}
|
||||
|
||||
push_attribute(attributes, Attributes(first, baseAttribute));
|
||||
|
||||
@@ -71,16 +38,16 @@ TextElementStringBase::print(char* first, char* last, Canvas::attributes_list* a
|
||||
}
|
||||
|
||||
char*
|
||||
TextElementString::copy_string(char* first, char* last, rpc::target_type target) {
|
||||
TextElementString::copy_string(char* first, char* last, [[maybe_unused]] rpc::target_type target) {
|
||||
extent_type length = std::min<extent_type>(last - first, m_string.size());
|
||||
|
||||
|
||||
std::memcpy(first, m_string.c_str(), length);
|
||||
|
||||
return first + length;
|
||||
}
|
||||
|
||||
char*
|
||||
TextElementCString::copy_string(char* first, char* last, rpc::target_type target) {
|
||||
TextElementCString::copy_string(char* first, char* last, [[maybe_unused]] rpc::target_type target) {
|
||||
extent_type length = std::min<extent_type>(last - first, m_length);
|
||||
|
||||
std::memcpy(first, m_string, length);
|
||||
@@ -104,22 +71,22 @@ TextElementCommand::print(char* first, char* last, Canvas::attributes_list* attr
|
||||
const std::string& str = result.as_string();
|
||||
|
||||
if (m_flags & flag_escape_hex) {
|
||||
first = rak::transform_hex(str.c_str(), str.c_str() + str.size(), first, last);
|
||||
first = torrent::utils::transform_to_hex(str.c_str(), str.c_str() + str.size(), first, last);
|
||||
|
||||
} else if (m_flags & flag_escape_html) {
|
||||
first = rak::copy_escape_html(str.c_str(), str.c_str() + str.size(), first, last);
|
||||
first = torrent::utils::copy_escape_html(str.c_str(), str.c_str() + str.size(), first, last);
|
||||
|
||||
} else {
|
||||
size_t length = std::min<size_t>(str.size(), std::distance(first, last));
|
||||
|
||||
std::memcpy(first, str.c_str(), length);
|
||||
first += std::min<size_t>(str.size(), length);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case torrent::Object::TYPE_VALUE:
|
||||
{
|
||||
{
|
||||
first += std::min<ptrdiff_t>(std::max(snprintf(first, last - first + 1, "%lld", (long long int)result.as_value()), 0), last - first + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RTORRENT_DISPLAY_TEXT_ELEMENT_STRING_H
|
||||
#define RTORRENT_DISPLAY_TEXT_ELEMENT_STRING_H
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ print_download_status(char* first, char* last, core::Download* d) {
|
||||
|
||||
if (d->is_hash_checking()) {
|
||||
first = print_buffer(first, last, "Checking hash [%2i%%]",
|
||||
(d->download()->chunks_hashed() * 100) / d->download()->file_list()->size_chunks());
|
||||
d->download()->file_list()->size_chunks() != 0 ? (d->download()->chunks_hashed() * 100) / d->download()->file_list()->size_chunks() : 0);
|
||||
|
||||
} else if (d->tracker_controller().has_active_trackers_not_scrape()) {
|
||||
auto tracker = d->tracker_controller().find_if([](const auto& t) {
|
||||
@@ -208,7 +208,7 @@ print_download_info_compact(char* first, char* last, core::Download* d) {
|
||||
if (d->is_done())
|
||||
first = print_buffer(first, last, " 100%% ");
|
||||
else if (d->is_open())
|
||||
first = print_buffer(first, last, " %2u%% ",(d->download()->file_list()->completed_chunks() * 100) / d->download()->file_list()->size_chunks());
|
||||
first = print_buffer(first, last, " %2u%% ", d->download()->file_list()->size_chunks() != 0 ? (d->download()->file_list()->completed_chunks() * 100) / d->download()->file_list()->size_chunks() : 0);
|
||||
else
|
||||
first = print_buffer(first, last, " ");
|
||||
|
||||
@@ -260,7 +260,7 @@ print_download_percentage_done(char* first, char* last, core::Download* d) {
|
||||
//return print_buffer(first, last, "[--%%]");
|
||||
return print_buffer(first, last, " ");
|
||||
else
|
||||
return print_buffer(first, last, "[%2u%%]", (d->download()->file_list()->completed_chunks() * 100) / d->download()->file_list()->size_chunks());
|
||||
return print_buffer(first, last, "[%2u%%]", d->download()->file_list()->size_chunks() != 0 ? (d->download()->file_list()->completed_chunks() * 100) / d->download()->file_list()->size_chunks() : 0);
|
||||
}
|
||||
|
||||
char*
|
||||
|
||||
+58
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <torrent/utils/scheduler.h>
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
#include "canvas.h"
|
||||
#include "globals.h"
|
||||
@@ -102,6 +102,63 @@ protected:
|
||||
torrent::utils::SchedulerEntry m_task_update;
|
||||
};
|
||||
|
||||
// Return a range with a distance of no more than __distance and
|
||||
// between __first and __last, centered on __middle1.
|
||||
template <typename _InputIter, typename _Distance>
|
||||
std::pair<_InputIter, _InputIter>
|
||||
advance_bidirectional(_InputIter __first, _InputIter __middle1, _InputIter __last, _Distance __distance) {
|
||||
_InputIter __middle2 = __middle1;
|
||||
|
||||
do {
|
||||
if (!__distance)
|
||||
break;
|
||||
|
||||
if (__middle2 != __last) {
|
||||
++__middle2;
|
||||
--__distance;
|
||||
|
||||
} else if (__middle1 == __first) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!__distance)
|
||||
break;
|
||||
|
||||
if (__middle1 != __first) {
|
||||
--__middle1;
|
||||
--__distance;
|
||||
|
||||
} else if (__middle2 == __last) {
|
||||
break;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
return std::make_pair(__middle1, __middle2);
|
||||
}
|
||||
|
||||
template <typename _InputIter, typename _Distance>
|
||||
_InputIter
|
||||
advance_forward(_InputIter __first, _InputIter __last, _Distance __distance) {
|
||||
while (__first != __last && __distance != 0) {
|
||||
__first++;
|
||||
__distance--;
|
||||
}
|
||||
|
||||
return __first;
|
||||
}
|
||||
|
||||
template <typename _InputIter, typename _Distance>
|
||||
_InputIter
|
||||
advance_backward(_InputIter __first, _InputIter __last, _Distance __distance) {
|
||||
while (__first != __last && __distance != 0) {
|
||||
__first--;
|
||||
__distance--;
|
||||
}
|
||||
|
||||
return __first;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "display/window_download_chunks_seen.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/bitfield.h>
|
||||
#include <torrent/data/block.h>
|
||||
#include <torrent/data/block_list.h>
|
||||
#include <torrent/data/transfer_list.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "core/download.h"
|
||||
|
||||
#include "window_download_chunks_seen.h"
|
||||
|
||||
namespace display {
|
||||
|
||||
WindowDownloadChunksSeen::WindowDownloadChunksSeen(core::Download* d, unsigned int *focus) :
|
||||
@@ -82,7 +82,7 @@ WindowDownloadChunksSeen::redraw() {
|
||||
attr = A_BOLD;
|
||||
}
|
||||
|
||||
m_canvas->print_char(attr | rak::value_to_hexchar<0>(std::min<uint8_t>(*chunk, 0xF)));
|
||||
m_canvas->print_char(attr | torrent::utils::value_to_hex0(std::min<uint8_t>(*chunk, 0xF)));
|
||||
chunk++;
|
||||
|
||||
if ((chunk - seen) % 10 == 0) {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "display/color_map.h"
|
||||
#include <rak/algorithm.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "core/download.h"
|
||||
#include "core/view.h"
|
||||
#include "display/canvas.h"
|
||||
#include "display/utils.h"
|
||||
#include "display/window_download_list.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
#include "canvas.h"
|
||||
#include "globals.h"
|
||||
#include "utils.h"
|
||||
#include "window_download_list.h"
|
||||
|
||||
namespace display {
|
||||
|
||||
WindowDownloadList::WindowDownloadList() :
|
||||
@@ -104,10 +102,10 @@ WindowDownloadList::redraw() {
|
||||
|
||||
typedef std::pair<core::View::iterator, core::View::iterator> Range;
|
||||
|
||||
Range range = rak::advance_bidirectional(m_view->begin_visible(),
|
||||
m_view->focus() != m_view->end_visible() ? m_view->focus() : m_view->begin_visible(),
|
||||
m_view->end_visible(),
|
||||
page_size(layout_name));
|
||||
Range range = advance_bidirectional(m_view->begin_visible(),
|
||||
m_view->focus() != m_view->end_visible() ? m_view->focus() : m_view->begin_visible(),
|
||||
m_view->end_visible(),
|
||||
page_size(layout_name));
|
||||
|
||||
// Make sure we properly fill out the last lines so it looks like
|
||||
// there are more torrents, yet don't hide it if we got the last one
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/data/block_list.h>
|
||||
#include <torrent/data/transfer_list.h>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ WindowHttpQueue::WindowHttpQueue(core::HttpQueue* q) :
|
||||
set_active(false);
|
||||
|
||||
m_conn_insert = m_queue->signal_insert().insert(m_queue->signal_insert().end(), [this](auto h) { receive_insert(h); });
|
||||
m_conn_erase = m_queue->signal_erase().insert(m_queue->signal_insert().end(), [this](auto h) { receive_erase(h); });
|
||||
m_conn_erase = m_queue->signal_erase().insert(m_queue->signal_erase().end(), [this](auto h) { receive_erase(h); });
|
||||
|
||||
m_task_deactivate.slot() = [this] {
|
||||
if (!m_container.empty())
|
||||
|
||||
@@ -12,7 +12,7 @@ WindowInput::redraw() {
|
||||
m_canvas->erase();
|
||||
m_canvas->print(0, 0, "%s> %s", m_title.c_str(), m_input != NULL ? m_input->c_str() : "<NULL>");
|
||||
|
||||
if (m_focus)
|
||||
if (m_focus && m_input != NULL)
|
||||
m_canvas->set_attr(m_input->get_pos() + 2 + m_title.size(), 0, 1, A_REVERSE, COLOR_PAIR(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <ctime>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
#include "canvas.h"
|
||||
#include "utils.h"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "display/window_peer_list.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/data/block_transfer.h>
|
||||
@@ -9,11 +11,8 @@
|
||||
#include <torrent/peer/peer_info.h>
|
||||
|
||||
#include "core/download.h"
|
||||
#include "rak/algorithm.h"
|
||||
|
||||
#include "canvas.h"
|
||||
#include "utils.h"
|
||||
#include "window_peer_list.h"
|
||||
#include "display/canvas.h"
|
||||
#include "display/utils.h"
|
||||
|
||||
namespace display {
|
||||
|
||||
@@ -50,10 +49,10 @@ WindowPeerList::redraw() {
|
||||
|
||||
typedef std::pair<PList::iterator, PList::iterator> Range;
|
||||
|
||||
Range range = rak::advance_bidirectional(m_list->begin(),
|
||||
*m_focus != m_list->end() ? *m_focus : m_list->begin(),
|
||||
m_list->end(),
|
||||
m_canvas->height() - y);
|
||||
Range range = advance_bidirectional(m_list->begin(),
|
||||
*m_focus != m_list->end() ? *m_focus : m_list->begin(),
|
||||
m_list->end(),
|
||||
m_canvas->height() - y);
|
||||
|
||||
if (m_download->download()->file_list()->size_chunks() <= 0)
|
||||
throw std::logic_error("WindowPeerList::redraw() m_slotChunksTotal() returned invalid value");
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
#include "window_tracker_list.h"
|
||||
|
||||
#include <rak/algorithm.h>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/tracker/tracker.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "core/download.h"
|
||||
|
||||
@@ -37,7 +36,7 @@ WindowTrackerList::redraw() {
|
||||
if (tc_size == 0 || *m_focus >= tc_size)
|
||||
return;
|
||||
|
||||
auto range = rak::advance_bidirectional<unsigned int>(0, *m_focus, tc_size, (m_canvas->height() - 1) / 2);
|
||||
auto range = advance_bidirectional<unsigned int>(0, *m_focus, tc_size, (m_canvas->height() - 1) / 2);
|
||||
auto group = tc.at(range.first).group();
|
||||
|
||||
while (range.first != range.second) {
|
||||
@@ -63,7 +62,7 @@ WindowTrackerList::redraw() {
|
||||
|
||||
m_canvas->print(0, pos++, "%s Id: %s Counters: %uf / %us (%u) %s S/L/D: %u/%u/%u (%u/%u)",
|
||||
state,
|
||||
rak::copy_escape_html(tracker.tracker_id()).c_str(),
|
||||
torrent::utils::copy_escape_html_str(tracker.tracker_id()).c_str(),
|
||||
tracker_state.failed_counter(),
|
||||
tracker_state.success_counter(),
|
||||
tracker_state.scrape_counter(),
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
rpc::ip_table_list ip_tables;
|
||||
@@ -14,13 +15,13 @@ expand_path(const std::string& path) {
|
||||
return std::string();
|
||||
|
||||
if (path[0] == '~') {
|
||||
if (path.size() < 2 || path[1] != '/')
|
||||
throw torrent::input_error("Could not expand ~ in session path, only ~/<path> is supported.");
|
||||
if (path.size() >= 2 && path[1] != '/')
|
||||
throw torrent::input_error("Could not expand ~ in path, only '~' or '~/...' is supported.");
|
||||
|
||||
const char* home = std::getenv("HOME");
|
||||
|
||||
if (home == nullptr || *home == '\0')
|
||||
throw torrent::input_error("Could not expand ~ in session path, HOME environment variable not set.");
|
||||
throw torrent::input_error("Could not expand ~ in path, HOME environment variable not set.");
|
||||
|
||||
return home + path.substr(1);
|
||||
}
|
||||
|
||||
+4
-4
@@ -22,8 +22,8 @@ class SessionManager;
|
||||
|
||||
namespace scgi_thread {
|
||||
|
||||
torrent::utils::Thread* thread();
|
||||
std::thread::id thread_id();
|
||||
torrent::system::Thread* thread();
|
||||
std::thread::id thread_id();
|
||||
|
||||
void callback(void* target, std::function<void ()>&& fn);
|
||||
void cancel_callback(void* target);
|
||||
@@ -38,8 +38,8 @@ void set_rpc_log(const std::string& filename);
|
||||
|
||||
namespace session_thread {
|
||||
|
||||
torrent::utils::Thread* thread();
|
||||
std::thread::id thread_id();
|
||||
torrent::system::Thread* thread();
|
||||
std::thread::id thread_id();
|
||||
|
||||
void callback(void* target, std::function<void ()>&& fn);
|
||||
void cancel_callback(void* target);
|
||||
|
||||
+48
-2
@@ -1,7 +1,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <rak/algorithm.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -10,6 +10,52 @@
|
||||
|
||||
#include "path_input.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename _Value>
|
||||
struct compare_base {
|
||||
bool operator () (const _Value& complete, const _Value& base) const {
|
||||
return !complete.compare(0, base.size(), base);
|
||||
}
|
||||
};
|
||||
|
||||
// Count the number of elements from the start of the containers to
|
||||
// the first inequal element.
|
||||
template <typename _InputIter1, typename _InputIter2>
|
||||
typename std::iterator_traits<_InputIter1>::difference_type
|
||||
count_base(_InputIter1 __first1, _InputIter1 __last1,
|
||||
_InputIter2 __first2, _InputIter2 __last2) {
|
||||
|
||||
typename std::iterator_traits<_InputIter1>::difference_type __n = 0;
|
||||
|
||||
for ( ;__first1 != __last1 && __first2 != __last2; ++__first1, ++__first2, ++__n)
|
||||
if (*__first1 != *__first2)
|
||||
return __n;
|
||||
|
||||
return __n;
|
||||
}
|
||||
|
||||
template <typename _Return, typename _InputIter, typename _Ftor>
|
||||
_Return
|
||||
make_base(_InputIter __first, _InputIter __last, _Ftor __ftor) {
|
||||
if (__first == __last)
|
||||
return "";
|
||||
|
||||
_Return __base = __ftor(*__first++);
|
||||
|
||||
for ( ;__first != __last; ++__first) {
|
||||
auto __pos = count_base(__base.begin(), __base.end(),
|
||||
__ftor(*__first).begin(), __ftor(*__first).end());
|
||||
|
||||
if (__pos < (typename std::iterator_traits<_InputIter>::difference_type)__base.size())
|
||||
__base.resize(__pos);
|
||||
}
|
||||
|
||||
return __base;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace input {
|
||||
|
||||
bool
|
||||
@@ -59,7 +105,7 @@ PathInput::receive_do_complete() {
|
||||
if (r.first == r.second)
|
||||
return; // Show some nice colors here.
|
||||
|
||||
std::string base = rak::make_base<std::string>(r.first, r.second, std::mem_fn(&utils::directory_entry::s_name));
|
||||
std::string base = make_base<std::string>(r.first, r.second, [](auto& de) { return de.s_name; });
|
||||
|
||||
// Clear the path after the cursor to make this code cleaner. It's
|
||||
// not really nessesary to add the complexity just because someone
|
||||
|
||||
+40
-99
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "core/dht_manager.h"
|
||||
#include "core/download.h"
|
||||
#include "core/download_factory.h"
|
||||
#include "core/manager.h"
|
||||
#include "display/canvas.h"
|
||||
#include "display/window.h"
|
||||
@@ -30,15 +29,15 @@
|
||||
#include "rpc/command_scheduler_item.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "scgi/thread_scgi.h"
|
||||
#include "session/download_storer.h"
|
||||
#include "session/thread_session.h"
|
||||
#include "session/session_manager.h"
|
||||
#include "session/thread_session.h"
|
||||
#include "ui/root.h"
|
||||
#include "utils/directory.h"
|
||||
|
||||
#include "control.h"
|
||||
#include "command_helpers.h"
|
||||
#include "globals.h"
|
||||
#include "setup.h"
|
||||
#include "signal_handler.h"
|
||||
#include "option_parser.h"
|
||||
|
||||
@@ -50,37 +49,6 @@ void do_panic(int signum);
|
||||
void print_help();
|
||||
void initialize_commands();
|
||||
|
||||
void do_nothing() {}
|
||||
void do_nothing_str(const std::string&) {}
|
||||
|
||||
int
|
||||
parse_options(int argc, char** argv) {
|
||||
try {
|
||||
OptionParser optionParser;
|
||||
|
||||
// Converted.
|
||||
optionParser.insert_flag('h', std::bind(&print_help));
|
||||
optionParser.insert_flag('n', std::bind(&do_nothing_str, std::placeholders::_1));
|
||||
optionParser.insert_flag('D', std::bind(&do_nothing_str, std::placeholders::_1));
|
||||
optionParser.insert_flag('I', std::bind(&do_nothing_str, std::placeholders::_1));
|
||||
optionParser.insert_flag('K', std::bind(&do_nothing_str, std::placeholders::_1));
|
||||
|
||||
optionParser.insert_option('b', std::bind(&rpc::call_command_set_string, "network.bind_address.set", std::placeholders::_1));
|
||||
optionParser.insert_option('d', std::bind(&rpc::call_command_set_string, "directory.default.set", std::placeholders::_1));
|
||||
optionParser.insert_option('i', std::bind(&rpc::call_command_set_string, "ip", std::placeholders::_1));
|
||||
optionParser.insert_option('p', std::bind(&rpc::call_command_set_string, "network.port_range.set", std::placeholders::_1));
|
||||
optionParser.insert_option('s', std::bind(&rpc::call_command_set_string, "session", std::placeholders::_1));
|
||||
|
||||
optionParser.insert_option('O', std::bind(&rpc::parse_command_single_std, std::placeholders::_1));
|
||||
optionParser.insert_option_list('o', std::bind(&rpc::call_command_set_std_string, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
return optionParser.process(argc, argv);
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Failed to parse command line option: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
initialize_rpc_slots() {
|
||||
rpc::rpc.slot_find_download() = [](const char* hash) {
|
||||
@@ -110,43 +78,6 @@ initialize_rpc_slots() {
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
load_session_torrents() {
|
||||
utils::Directory entries = session::DownloadStorer::get_formated_entries(session_thread::manager()->path());
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
// We don't really support session torrents that are links. These
|
||||
// would be overwritten anyway on exit, and thus not really be
|
||||
// useful.
|
||||
if (!entry.is_file())
|
||||
continue;
|
||||
|
||||
core::DownloadFactory* f = new core::DownloadFactory(control->core());
|
||||
|
||||
// Replace with session torrent flag.
|
||||
f->set_session(true);
|
||||
f->set_init_load(true);
|
||||
f->slot_finished([f](){ delete f; });
|
||||
f->load(entries.path() + entry.s_name);
|
||||
f->commit();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
load_arg_torrents(char** first, char** last) {
|
||||
//std::for_each(begin, end, std::bind1st(std::mem_fun(&core::Manager::insert), &control->get_core()));
|
||||
for (; first != last; ++first) {
|
||||
core::DownloadFactory* f = new core::DownloadFactory(control->core());
|
||||
|
||||
// Replace with session torrent flag.
|
||||
f->set_start(true);
|
||||
f->set_init_load(true);
|
||||
f->slot_finished([f](){ delete f; });
|
||||
f->load(*first);
|
||||
f->commit();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
client_perform() {
|
||||
// Use throw exclusively.
|
||||
@@ -176,19 +107,36 @@ main(int argc, char** argv) {
|
||||
srandom(random_seed);
|
||||
srand48(random_seed);
|
||||
|
||||
torrent::initialize_main_thread();
|
||||
torrent::log_initialize();
|
||||
|
||||
// TODO: Create a fake thread object for initializing other processes and enabling logging.
|
||||
torrent::initialize_main_thread();
|
||||
|
||||
SignalHandler::set_ignore(SIGPIPE);
|
||||
SignalHandler::set_handler(SIGSEGV, std::bind(&do_panic, SIGSEGV));
|
||||
SignalHandler::set_handler(SIGILL, std::bind(&do_panic, SIGILL));
|
||||
SignalHandler::set_handler(SIGFPE, std::bind(&do_panic, SIGFPE));
|
||||
|
||||
// Limited list of commands with the following format:
|
||||
//
|
||||
// cat ~/.rtorrent.rc:
|
||||
//
|
||||
// # do:log.open_file=system,/usr/rakshasa/system.log
|
||||
// # do:log.add_output=system,system
|
||||
//
|
||||
parse_config_file(argc, argv, [](auto& path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
parse_config_file_comments(path);
|
||||
});
|
||||
|
||||
control = new Control;
|
||||
|
||||
SignalHandler::set_ignore(SIGPIPE);
|
||||
SignalHandler::set_handler(SIGINT, std::bind(&Control::receive_normal_shutdown, control));
|
||||
SignalHandler::set_handler(SIGHUP, std::bind(&Control::receive_normal_shutdown, control));
|
||||
SignalHandler::set_handler(SIGTERM, std::bind(&Control::receive_quick_shutdown, control));
|
||||
SignalHandler::set_handler(SIGWINCH, std::bind(&display::Manager::force_redraw, control->display()));
|
||||
SignalHandler::set_handler(SIGSEGV, std::bind(&do_panic, SIGSEGV));
|
||||
SignalHandler::set_handler(SIGILL, std::bind(&do_panic, SIGILL));
|
||||
SignalHandler::set_handler(SIGFPE, std::bind(&do_panic, SIGFPE));
|
||||
|
||||
SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus);
|
||||
|
||||
@@ -199,8 +147,8 @@ main(int argc, char** argv) {
|
||||
// platforms that do not properly pass signals to the target
|
||||
// threads. Use '--enable-interrupt-socket' when configuring
|
||||
// LibTorrent to enable this workaround.
|
||||
if (torrent::utils::Thread::should_handle_sigusr1())
|
||||
SignalHandler::set_handler(SIGUSR1, std::bind(&do_nothing));
|
||||
if (torrent::system::Thread::should_handle_sigusr1())
|
||||
SignalHandler::set_handler(SIGUSR1, []() {});
|
||||
|
||||
torrent::log_add_group_output(torrent::LOG_NOTICE, "important");
|
||||
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important");
|
||||
@@ -419,6 +367,7 @@ main(int argc, char** argv) {
|
||||
// Deprecate:
|
||||
|
||||
CMD2_VAR_STRING("dht.throttle.name", "deprecated");
|
||||
rpc::rpc.mark_safe("dht.throttle.name");
|
||||
|
||||
CMD2_REDIRECT("network.http.max_open", "network.http.max_total_connections");
|
||||
CMD2_REDIRECT("network.http.max_open.set", "network.http.max_total_connections.set");
|
||||
@@ -458,23 +407,16 @@ main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
int firstArg = parse_options(argc, argv);
|
||||
int firstArg = parse_main_options(argc, argv);
|
||||
|
||||
if (OptionParser::has_flag('n', argc, argv)) {
|
||||
lt_log_print(torrent::LOG_WARN, "Ignoring rtorrent.rc.");
|
||||
} else {
|
||||
char* config_dir = std::getenv("XDG_CONFIG_HOME");
|
||||
char* home_dir = std::getenv("HOME");
|
||||
parse_config_file(argc, argv, [](auto& path) {
|
||||
if (path.empty()) {
|
||||
lt_log_print(torrent::LOG_WARN, "Ignoring rtorrent.rc.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config_dir != NULL && config_dir[0] == '/' &&
|
||||
access((std::string(config_dir) + "/rtorrent/rtorrent.rc").c_str(), F_OK) != -1) {
|
||||
rpc::parse_command_single(rpc::make_target(), "try_import = " + std::string(config_dir) + "/rtorrent/rtorrent.rc");
|
||||
} else if (home_dir != NULL && access((std::string(home_dir) + "/.config/rtorrent/rtorrent.rc").c_str(), F_OK) != -1) {
|
||||
rpc::parse_command_single(rpc::make_target(), "try_import = ~/.config/rtorrent/rtorrent.rc");
|
||||
} else {
|
||||
rpc::parse_command_single(rpc::make_target(), "try_import = ~/.rtorrent.rc");
|
||||
}
|
||||
}
|
||||
rpc::parse_command_single(rpc::make_target(), "try_import=" + path);
|
||||
});
|
||||
|
||||
LT_LOG("seeded srandom and srand48 (seed:%u)", random_seed);
|
||||
|
||||
@@ -484,11 +426,8 @@ main(int argc, char** argv) {
|
||||
// Load session torrents and perform scheduled tasks to ensure
|
||||
// session torrents are loaded before arg torrents.
|
||||
control->dht_manager()->load_dht_cache();
|
||||
load_session_torrents();
|
||||
|
||||
// TODO: Check if this is required.
|
||||
// rak::priority_queue_perform(&taskScheduler, cachedTime);
|
||||
|
||||
load_session_torrents(session_thread::manager()->path());
|
||||
load_arg_torrents(argv + firstArg, argv + argc);
|
||||
|
||||
// Make sure we update the display before any scheduled tasks can
|
||||
@@ -499,7 +438,7 @@ main(int argc, char** argv) {
|
||||
|
||||
rpc::commands.call_catch("event.system.startup_done", rpc::make_target(), "startup_done", "System startup_done event action failed: ");
|
||||
|
||||
torrent::utils::Thread::self()->event_loop();
|
||||
torrent::system::Thread::self()->event_loop();
|
||||
|
||||
control->core()->download_list()->session_save();
|
||||
control->cleanup();
|
||||
@@ -615,7 +554,9 @@ do_panic(int signum) {
|
||||
// Use the default signal handler in the future to avoid infinit
|
||||
// loops.
|
||||
SignalHandler::set_default(signum);
|
||||
display::Canvas::cleanup();
|
||||
|
||||
if (control != nullptr)
|
||||
display::Canvas::cleanup();
|
||||
|
||||
std::stringstream output;
|
||||
|
||||
|
||||
+6
-37
@@ -1,39 +1,7 @@
|
||||
// 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "option_parser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
@@ -42,8 +10,6 @@
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "option_parser.h"
|
||||
|
||||
void
|
||||
OptionParser::insert_flag(char c, slot_string s) {
|
||||
m_container[c].m_slot = s;
|
||||
@@ -137,8 +103,11 @@ void
|
||||
OptionParser::call_int_pair(slot_int_pair slot, const std::string& arg) {
|
||||
int a, b;
|
||||
|
||||
if (std::sscanf(arg.c_str(), "%u-%u", &a, &b) != 2)
|
||||
if (std::sscanf(arg.c_str(), "%d-%d", &a, &b) != 2)
|
||||
throw std::runtime_error("Invalid argument, \"" + arg + "\" should be \"a-b\"");
|
||||
|
||||
if (a < 0 || b < 0)
|
||||
throw std::runtime_error("Invalid argument, \"" + arg + "\" should be positive numbers");
|
||||
|
||||
slot(a, b);
|
||||
}
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RTORRENT_OPTION_PARSER_H
|
||||
#define RTORRENT_OPTION_PARSER_H
|
||||
|
||||
|
||||
@@ -98,11 +98,17 @@ CommandMap::call_command(const key_type& key, const mapped_type& arg, const targ
|
||||
if (itr == base_type::end())
|
||||
throw torrent::input_error("Command \"" + std::string(key) + "\" does not exist.");
|
||||
|
||||
if (!rpc.is_trusted() && !(itr->second.m_flags & flag_untrusted_safe))
|
||||
throw untrusted_error("Command \"" + std::string(key) + "\" is not allowed for untrusted connections.");
|
||||
|
||||
return itr->second.m_anySlot(&itr->second.m_variable, target, arg);
|
||||
}
|
||||
|
||||
const CommandMap::mapped_type
|
||||
CommandMap::call_command(iterator itr, const mapped_type& arg, const target_type& target) {
|
||||
if (!rpc.is_trusted() && !(itr->second.m_flags & flag_untrusted_safe))
|
||||
throw untrusted_error("Command \"" + itr->first + "\" is not allowed for untrusted connections.");
|
||||
|
||||
return itr->second.m_anySlot(&itr->second.m_variable, target, arg);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
static const int flag_file_target = 0x100;
|
||||
static const int flag_tracker_target = 0x200;
|
||||
|
||||
static const int flag_untrusted_safe = 0x400;
|
||||
|
||||
CommandMap() = default;
|
||||
|
||||
bool has(const std::string& key) const { return base_type::find(key) != base_type::end(); }
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <time.h>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/utils/chrono.h>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
#include "exec_file.h"
|
||||
#include "parse.h"
|
||||
@@ -41,8 +41,13 @@ ExecFile::execute(const char* file, char* const* argv, int flags) {
|
||||
|
||||
pid_t childPid = fork();
|
||||
|
||||
if (childPid == -1)
|
||||
if (childPid == -1) {
|
||||
if (flags & flag_capture) {
|
||||
::close(pipeFd[0]);
|
||||
::close(pipeFd[1]);
|
||||
}
|
||||
throw torrent::input_error("ExecFile::execute(...) Fork failed.");
|
||||
}
|
||||
|
||||
if (childPid == 0) {
|
||||
if (flags & flag_background) {
|
||||
|
||||
+6
-3
@@ -133,9 +133,12 @@ jsonrpc_call_command(const std::string& method, const json& params) {
|
||||
|
||||
params_object_list.erase(params_object_list.begin());
|
||||
|
||||
const auto& result = rpc::commands.call_command(itr, params_object, target);
|
||||
|
||||
return object_to_json(result);
|
||||
try {
|
||||
const auto& result = rpc::commands.call_command(itr, params_object, target);
|
||||
return object_to_json(result);
|
||||
} catch (untrusted_error& e) {
|
||||
throw rpc_error(JSONRPC_METHOD_NOT_FOUND_ERROR, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
json
|
||||
|
||||
+12
-10
@@ -7,12 +7,13 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "rpc/lua.h"
|
||||
|
||||
#ifdef HAVE_LUA
|
||||
#include <lua.hpp>
|
||||
#endif
|
||||
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/object.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "core/download.h"
|
||||
#include "rpc/command.h"
|
||||
@@ -169,19 +170,18 @@ object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* t
|
||||
}
|
||||
|
||||
// Length of SHA1 hash is 40
|
||||
if (target_string.size() < 40) {
|
||||
if (target_string.size() < 40)
|
||||
throw torrent::input_error("invalid parameters: invalid target");
|
||||
}
|
||||
|
||||
char type = 'd';
|
||||
std::string hash;
|
||||
std::string index;
|
||||
|
||||
const auto& delim_pos = target_string.find_first_of(':', 40);
|
||||
|
||||
if (delim_pos == target_string.npos || delim_pos + 2 >= target_string.size()) {
|
||||
if (require_index) {
|
||||
if (require_index)
|
||||
throw torrent::input_error("invalid parameters: no index");
|
||||
}
|
||||
|
||||
hash = target_string;
|
||||
|
||||
@@ -217,12 +217,14 @@ object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* t
|
||||
|
||||
case 'p':
|
||||
{
|
||||
if (index.size() < 40) {
|
||||
throw torrent::input_error("Not a hash string.");
|
||||
}
|
||||
if (index.size() != 40)
|
||||
throw torrent::input_error("invalid parameters: target is not 40 bytes long");
|
||||
|
||||
torrent::HashString hash;
|
||||
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
||||
|
||||
if (torrent::utils::transform_from_hex(index.c_str(), index.c_str() + 40, hash) != hash.end())
|
||||
throw torrent::input_error("invalid parameters: target is not a hex string");
|
||||
|
||||
*target = rpc::make_target(command_base::target_peer, rpc.slot_find_peer()(download, hash));
|
||||
}
|
||||
break;
|
||||
@@ -381,7 +383,7 @@ execute_lua(LuaEngine* engine, rpc::target_type target_type, torrent::Object con
|
||||
case (command_base::target_download):
|
||||
core::Download* dl_target = (core::Download*)target_type.second;
|
||||
torrent::HashString infohash = dl_target->info()->hash();
|
||||
target_string = rak::transform_hex_str(infohash);
|
||||
target_string = torrent::utils::transform_to_hex_str(infohash);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "object_storage.h"
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
// The object_storage type is responsible for storing variables,
|
||||
// commands, command lists and other types encoded in torrent::Object
|
||||
// format.
|
||||
@@ -42,8 +8,8 @@
|
||||
#include <cstring>
|
||||
#include <unordered_map>
|
||||
#include <torrent/object.h>
|
||||
#include <torrent/utils/unordered_vector.h>
|
||||
|
||||
#include "rak/unordered_vector.h"
|
||||
#include "command.h"
|
||||
#include "fixed_key.h"
|
||||
|
||||
@@ -59,8 +25,8 @@ typedef std::unordered_map<fixed_key_type<64>, object_storage_node, hash_fixed_k
|
||||
class object_storage : private object_storage_base_type {
|
||||
public:
|
||||
// Should really change rlookup_type into a set with pair values.
|
||||
typedef object_storage_base_type base_type;
|
||||
typedef std::map<std::string, rak::unordered_vector<base_type::value_type*> > rlookup_type;
|
||||
typedef object_storage_base_type base_type;
|
||||
typedef std::map<std::string, torrent::utils::unordered_vector<base_type::value_type*> > rlookup_type;
|
||||
|
||||
using base_type::key_type;
|
||||
using base_type::value_type;
|
||||
|
||||
+9
-3
@@ -120,11 +120,17 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
|
||||
case 'b':
|
||||
case 'B': ++last; break;
|
||||
case 'k':
|
||||
case 'K': *value = *value << 10; ++last; break;
|
||||
case 'K':
|
||||
if (*value > (int64_t)0x1FFFFFFFFFFFFF) return src; // overflow guard
|
||||
*value = *value << 10; ++last; break;
|
||||
case 'm':
|
||||
case 'M': *value = *value << 20; ++last; break;
|
||||
case 'M':
|
||||
if (*value > (int64_t)0x7FFFFFFFFFF) return src; // overflow guard
|
||||
*value = *value << 20; ++last; break;
|
||||
case 'g':
|
||||
case 'G': *value = *value << 30; ++last; break;
|
||||
case 'G':
|
||||
if (*value > (int64_t)0x1FFFFFFFF) return src; // overflow guard
|
||||
*value = *value << 30; ++last; break;
|
||||
// case ' ':
|
||||
// case '\0': *value = *value * unit; break;
|
||||
// default: throw torrent::input_error("Could not parse value.");
|
||||
|
||||
@@ -151,23 +151,21 @@ parse_command_file(const std::string& path) {
|
||||
try {
|
||||
unsigned int getCount = 0;
|
||||
|
||||
while (file.good()
|
||||
&& !file.getline(buffer + getCount, 4096 - getCount).fail()) {
|
||||
|
||||
while (file.good() && !file.getline(buffer + getCount, 4096 - getCount).fail()) {
|
||||
if (file.gcount() == 0)
|
||||
throw torrent::internal_error("parse_command_file(...) file.gcount() == 0.");
|
||||
|
||||
int lineLength = file.gcount() - 1;
|
||||
int line_length = file.gcount() - 1;
|
||||
// In case we are at the end of the file and the last character is
|
||||
// not a line feed, we'll just increase the read character count so
|
||||
// that the last would also be included in option line.
|
||||
if (file.eof() && file.get() != '\n')
|
||||
lineLength++;
|
||||
line_length++;
|
||||
|
||||
int escaped = parse_count_escaped(buffer + getCount, buffer + getCount + lineLength);
|
||||
int escaped = parse_count_escaped(buffer + getCount, buffer + getCount + line_length);
|
||||
|
||||
lineNumber++;
|
||||
getCount += lineLength;
|
||||
getCount += line_length;
|
||||
|
||||
if (getCount == 4096 - 1)
|
||||
throw torrent::input_error("Exceeded max line length.");
|
||||
@@ -258,9 +256,9 @@ command_function_call_object(const torrent::Object& cmd, target_type target, con
|
||||
rpc::command_base::pop_stack(&stack, last_stack);
|
||||
return result;
|
||||
|
||||
} catch (torrent::bencode_error& e) {
|
||||
} catch (...) {
|
||||
rpc::command_base::pop_stack(&stack, last_stack);
|
||||
throw e;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-8
@@ -3,6 +3,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "parse_commands.h"
|
||||
#include "rpc/rpc_manager.h"
|
||||
@@ -13,6 +14,19 @@ CommandMap commands;
|
||||
RpcManager rpc;
|
||||
ExecFile execFile;
|
||||
|
||||
// Trusted/untrusted XMLRPC connection model.
|
||||
//
|
||||
// The trust state is set per-request by the SCGI layer based on the
|
||||
// UNTRUSTED_CONNECTION header. Commands without flag_untrusted_safe
|
||||
// are blocked for untrusted connections. The check is in
|
||||
// CommandMap::call_command(), which catches all command execution
|
||||
// including nested calls through argument expansion.
|
||||
|
||||
bool
|
||||
RpcManager::is_trusted() const {
|
||||
return m_trusted;
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target, std::function<void()>* deleter) {
|
||||
if (!obj.is_string())
|
||||
@@ -34,11 +48,11 @@ RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::ta
|
||||
|
||||
const auto& delim_pos = target_string.find_first_of(':', 40);
|
||||
|
||||
if (delim_pos == target_string.npos ||
|
||||
delim_pos + 2 >= target_string.size()) {
|
||||
if (delim_pos == target_string.npos || delim_pos + 2 >= target_string.size()) {
|
||||
if (require_index) {
|
||||
throw torrent::input_error("invalid parameters: no index");
|
||||
}
|
||||
|
||||
hash = target_string;
|
||||
|
||||
} else {
|
||||
@@ -74,15 +88,15 @@ RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::ta
|
||||
break;
|
||||
|
||||
case 'p': {
|
||||
if (index.size() < 40) {
|
||||
throw torrent::input_error("invalid parameters: not a hash string.");
|
||||
}
|
||||
if (index.size() != 40)
|
||||
throw torrent::input_error("invalid parameters: target is not 40 bytes long");
|
||||
|
||||
torrent::HashString hash;
|
||||
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
||||
|
||||
*target = rpc::make_target(command_base::target_peer,
|
||||
rpc.slot_find_peer()(download, hash));
|
||||
if (torrent::utils::transform_from_hex(index.c_str(), index.c_str() + 40, hash) != hash.end())
|
||||
throw torrent::input_error("invalid parameters: target is not a hex string");
|
||||
|
||||
*target = rpc::make_target(command_base::target_peer, rpc.slot_find_peer()(download, hash));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -124,6 +138,21 @@ RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_r
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RpcManager::process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
|
||||
bool previous = m_trusted;
|
||||
m_trusted = false;
|
||||
|
||||
try {
|
||||
bool result = process(type, in_buffer, length, callback);
|
||||
m_trusted = previous;
|
||||
return result;
|
||||
} catch (...) {
|
||||
m_trusted = previous;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::initialize_handlers() {
|
||||
if (m_handlers_initialized)
|
||||
@@ -175,4 +204,14 @@ RpcManager::insert_command(const char* name, const char* parm, const char* doc)
|
||||
m_jsonrpc.insert_command(name, parm, doc);
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::mark_safe(const std::string& key) {
|
||||
auto itr = commands.find(key);
|
||||
|
||||
if (itr == commands.end())
|
||||
return;
|
||||
|
||||
itr->second.m_flags |= CommandMap::flag_untrusted_safe;
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
+43
-16
@@ -1,9 +1,11 @@
|
||||
#ifndef RTORRENT_RPC_MANAGER_H
|
||||
#define RTORRENT_RPC_MANAGER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <torrent/common.h>
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
#include "rpc/command.h"
|
||||
#include "rpc/command_map.h"
|
||||
@@ -34,6 +36,12 @@ private:
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
class untrusted_error : public torrent::input_error {
|
||||
public:
|
||||
using torrent::input_error::input_error;
|
||||
virtual ~untrusted_error() throw() = default;
|
||||
};
|
||||
|
||||
class RpcManager {
|
||||
public:
|
||||
using slot_download = std::function<core::Download*(const char*)>;
|
||||
@@ -48,32 +56,48 @@ public:
|
||||
RpcManager() = default;
|
||||
~RpcManager() = default;
|
||||
|
||||
bool is_handlers_initialized() const { return m_handlers_initialized; }
|
||||
bool is_handlers_initialized() const { return m_handlers_initialized; }
|
||||
|
||||
void initialize_handlers();
|
||||
void cleanup();
|
||||
void initialize_handlers();
|
||||
void cleanup();
|
||||
|
||||
int64_t size_limit() { return m_xmlrpc.size_limit(); };
|
||||
void set_size_limit(uint64_t size) { m_xmlrpc.set_size_limit(size); };
|
||||
int64_t size_limit() { return m_xmlrpc.size_limit(); };
|
||||
void set_size_limit(uint64_t size) { m_xmlrpc.set_size_limit(size); };
|
||||
|
||||
int dialect() { return m_xmlrpc.dialect(); }
|
||||
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
|
||||
int dialect() { return m_xmlrpc.dialect(); }
|
||||
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
|
||||
|
||||
bool is_type_enabled(RPCType type) const;
|
||||
void set_type_enabled(RPCType type, bool enabled);
|
||||
bool is_type_enabled(RPCType type) const;
|
||||
void set_type_enabled(RPCType type, bool enabled);
|
||||
|
||||
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);
|
||||
bool process_untrusted(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);
|
||||
void mark_safe(const std::string& key);
|
||||
|
||||
slot_download& slot_find_download() { return m_slot_find_download; }
|
||||
slot_file& slot_find_file() { return m_slot_find_file; }
|
||||
slot_tracker& slot_find_tracker() { return m_slot_find_tracker; }
|
||||
slot_peer& slot_find_peer() { return m_slot_find_peer; }
|
||||
bool scgi_allow_compression() const { return m_scgi_allow_compression; }
|
||||
void set_scgi_allow_compression(bool allow) { m_scgi_allow_compression = allow; }
|
||||
|
||||
static void object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target, std::function<void()>* deleter);
|
||||
unsigned int scgi_min_compress_size() const { return m_scgi_min_compress_size; }
|
||||
void set_scgi_min_compress_size(unsigned int size) { m_scgi_min_compress_size = size; }
|
||||
|
||||
slot_download& slot_find_download() { return m_slot_find_download; }
|
||||
slot_file& slot_find_file() { return m_slot_find_file; }
|
||||
slot_tracker& slot_find_tracker() { return m_slot_find_tracker; }
|
||||
slot_peer& slot_find_peer() { return m_slot_find_peer; }
|
||||
|
||||
// Trusted/untrusted XMLRPC connection model.
|
||||
//
|
||||
// When an SCGI request includes the UNTRUSTED_CONNECTION header,
|
||||
// commands without flag_untrusted_safe are blocked.
|
||||
bool is_trusted() const;
|
||||
|
||||
static void object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target, std::function<void()>* deleter);
|
||||
|
||||
private:
|
||||
bool m_trusted{true};
|
||||
|
||||
XmlRpc m_xmlrpc;
|
||||
JsonRpc m_jsonrpc;
|
||||
|
||||
@@ -81,6 +105,9 @@ private:
|
||||
bool m_is_jsonrpc_enabled{true};
|
||||
bool m_is_xmlrpc_enabled{true};
|
||||
|
||||
std::atomic<bool> m_scgi_allow_compression{false};
|
||||
std::atomic<unsigned int> m_scgi_min_compress_size{0};
|
||||
|
||||
slot_download m_slot_find_download;
|
||||
slot_file m_slot_find_file;
|
||||
slot_tracker m_slot_find_tracker;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/un.h>
|
||||
#include <torrent/connection_manager.h>
|
||||
@@ -80,6 +81,19 @@ SCgi::open_named(const std::string& filename) {
|
||||
m_path = filename;
|
||||
}
|
||||
|
||||
void
|
||||
SCgi::open_fd(int fd) {
|
||||
torrent::runtime::socket_manager()->open_event_or_throw(this, [&]() {
|
||||
if (!torrent::fd_set_nonblock(fd))
|
||||
throw torrent::resource_error("Could not set non-blocking on systemd fd: " +
|
||||
std::string(std::strerror(errno)));
|
||||
set_file_descriptor(fd);
|
||||
// fd is already bound and listening; no bind()/listen() needed.
|
||||
});
|
||||
|
||||
torrent::connection_manager()->inc_socket_count();
|
||||
}
|
||||
|
||||
void
|
||||
SCgi::open(sockaddr* sa, unsigned int length) {
|
||||
try {
|
||||
|
||||
+4
-4
@@ -2,7 +2,6 @@
|
||||
#define RTORRENT_RPC_SCGI_H
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <torrent/event.h>
|
||||
|
||||
@@ -21,15 +20,16 @@ public:
|
||||
|
||||
void open_port(sockaddr* sa, unsigned int length, bool dont_route);
|
||||
void open_named(const std::string& filename);
|
||||
void open_fd(int fd);
|
||||
|
||||
void activate();
|
||||
|
||||
void stop();
|
||||
|
||||
const std::string& path() const { return m_path; }
|
||||
const std::string& path() const { return m_path; }
|
||||
|
||||
int log_fd() const { return m_logFd; }
|
||||
void set_log_fd(int fd) { m_logFd = fd; }
|
||||
int log_fd() const { return m_logFd; }
|
||||
void set_log_fd(int fd) { m_logFd = fd; }
|
||||
|
||||
void event_read() override;
|
||||
void event_write() override;
|
||||
|
||||
+258
-160
@@ -2,23 +2,25 @@
|
||||
|
||||
#include "rpc/scgi_task.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/net/fd.h>
|
||||
#include <torrent/net/poll.h>
|
||||
#include <torrent/runtime/socket_manager.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "scgi.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "rpc/scgi.h"
|
||||
#include "utils/gzip.h"
|
||||
|
||||
namespace rpc {
|
||||
|
||||
@@ -26,16 +28,22 @@ void
|
||||
SCgiTask::open(SCgi* parent, int fd) {
|
||||
set_file_descriptor(fd);
|
||||
|
||||
m_buffer.reset(new char[default_buffer_size + 1]);
|
||||
|
||||
m_parent = parent;
|
||||
m_buffer_size = default_buffer_size;
|
||||
m_position = m_buffer.get();
|
||||
m_body = nullptr;
|
||||
m_position = 0;
|
||||
m_body = 0;
|
||||
|
||||
m_content_length = 0;
|
||||
m_content_type = XML;
|
||||
m_accepts_compression = false;
|
||||
|
||||
torrent::this_thread::poll()->open(this);
|
||||
torrent::this_thread::poll()->insert_read(this);
|
||||
torrent::this_thread::poll()->insert_error(this);
|
||||
|
||||
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||
|
||||
// Leave room for terminating nul byte for parsing the header.
|
||||
m_buffer.resize(default_buffer_size + 1);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -55,7 +63,7 @@ SCgiTask::close() {
|
||||
return;
|
||||
|
||||
torrent::main_thread::thread()->cancel_callback_and_wait(this);
|
||||
torrent::utils::Thread::self()->cancel_callback(this);
|
||||
torrent::system::Thread::self()->cancel_callback(this);
|
||||
|
||||
torrent::runtime::socket_manager()->close_event_or_throw(this, [this]() {
|
||||
torrent::this_thread::poll()->remove_and_close(this);
|
||||
@@ -64,14 +72,23 @@ SCgiTask::close() {
|
||||
set_file_descriptor(-1);
|
||||
});
|
||||
|
||||
// The callbacks are guaranteed to be finished/canceled at this point.
|
||||
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||
|
||||
m_buffer = nullptr;
|
||||
m_buffer.clear();
|
||||
}
|
||||
|
||||
void
|
||||
SCgiTask::event_read() {
|
||||
int bytes = ::recv(m_fileDesc, m_position, m_buffer_size - (m_position - m_buffer.get()), 0);
|
||||
int read_length = m_buffer.size() - m_position;
|
||||
|
||||
if (m_content_length == 0)
|
||||
read_length--;
|
||||
|
||||
if (read_length <= 0)
|
||||
throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read.");
|
||||
|
||||
int bytes = ::recv(m_fileDesc, m_buffer.data() + m_position, read_length, 0);
|
||||
|
||||
if (bytes <= 0) {
|
||||
if (bytes == 0 || !(errno == EAGAIN || errno == EINTR))
|
||||
@@ -82,99 +99,45 @@ SCgiTask::event_read() {
|
||||
|
||||
// The buffer has space to nul-terminate to ease the parsing below.
|
||||
m_position += bytes;
|
||||
*m_position = '\0';
|
||||
|
||||
if (m_body == NULL) {
|
||||
// Don't bother caching the parsed values, as we're likely to
|
||||
// receive all the data we need the first time.
|
||||
char* current;
|
||||
if (m_content_length == 0) {
|
||||
// While reading the header, we leave room in the buffer for a nul byte.
|
||||
m_buffer[m_position] = '\0';
|
||||
|
||||
int header_size = strtol(m_buffer.get(), ¤t, 0);
|
||||
// Don't bother caching the parsed values, as we're likely to receive all the data we need the
|
||||
// first time.
|
||||
unsigned int header_size{};
|
||||
|
||||
if (current == m_position)
|
||||
auto [current, ec] = std::from_chars(m_buffer.data(), m_buffer.data() + m_position, header_size);
|
||||
|
||||
// If the request doesn't start with an integer or if it didn't end in ':', then close the
|
||||
// connection.
|
||||
|
||||
if (current == m_buffer.data())
|
||||
return;
|
||||
|
||||
// If the request doesn't start with an integer or if it didn't
|
||||
// end in ':', then close the connection.
|
||||
if (current == m_buffer.get() || *current != ':' || header_size < 17 || header_size > max_header_size)
|
||||
if (*current != ':' || header_size < 17 || header_size > max_header_size)
|
||||
goto event_read_failed;
|
||||
|
||||
if (std::distance(++current, m_position) < header_size + 1)
|
||||
current++;
|
||||
|
||||
// The header size starts after the ':' and ends at the first ','.
|
||||
if ((m_buffer.data() + m_position) - current < static_cast<int>(header_size) + 1)
|
||||
return;
|
||||
|
||||
// We'll parse this fully below, but the SCGI spec requires it to
|
||||
// be the first header.
|
||||
if (std::memcmp(current, "CONTENT_LENGTH", 15) != 0)
|
||||
// Check for ',' after the header, if it's not there, then close the connection.
|
||||
if (*(current + header_size) != ',')
|
||||
goto event_read_failed;
|
||||
|
||||
std::string content_type = "";
|
||||
size_t content_length = 0;
|
||||
const char* header_end = current + header_size;
|
||||
|
||||
// Parse out the null-terminated header keys and values, with
|
||||
// checks to ensure it doesn't scan beyond the limits of the
|
||||
// header
|
||||
while (current < header_end) {
|
||||
char* key = current;
|
||||
char* key_end = static_cast<char*>(std::memchr(current, '\0', header_end - current));
|
||||
|
||||
if (!key_end)
|
||||
goto event_read_failed;
|
||||
|
||||
current = key_end + 1;
|
||||
|
||||
if (current >= header_end)
|
||||
goto event_read_failed;
|
||||
|
||||
char* value = current;
|
||||
char* value_end = static_cast<char*>(std::memchr(current, '\0', header_end - current));
|
||||
|
||||
if (!value_end)
|
||||
goto event_read_failed;
|
||||
|
||||
current = value_end + 1;
|
||||
|
||||
if (strcmp(key, "CONTENT_LENGTH") == 0) {
|
||||
char* content_pos;
|
||||
content_length = strtol(value, &content_pos, 10);
|
||||
if (*content_pos != '\0' || content_length <= 0 || content_length > max_content_size)
|
||||
goto event_read_failed;
|
||||
} else if (strcmp(key, "CONTENT_TYPE") == 0) {
|
||||
content_type = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != header_end)
|
||||
// The header must start with "CONTENT_LENGTH" followed by a null byte.
|
||||
if (std::memcmp(current, "CONTENT_LENGTH", 14+1) != 0)
|
||||
goto event_read_failed;
|
||||
|
||||
if (content_length <= 0)
|
||||
if (!parse_headers(current, header_size))
|
||||
goto event_read_failed;
|
||||
|
||||
m_body = current + 1;
|
||||
header_size = std::distance(m_buffer.get(), m_body);
|
||||
|
||||
if (!detect_content_type(content_type))
|
||||
goto event_read_failed;
|
||||
|
||||
if ((unsigned int)(content_length + header_size) < m_buffer_size) {
|
||||
m_buffer_size = content_length + header_size;
|
||||
|
||||
} else if ((unsigned int)content_length <= default_buffer_size) {
|
||||
m_buffer_size = content_length;
|
||||
|
||||
std::memmove(m_buffer.get(), m_body, std::distance(m_body, m_position));
|
||||
m_position = m_buffer.get() + std::distance(m_body, m_position);
|
||||
m_body = m_buffer.get();
|
||||
|
||||
} else {
|
||||
realloc_buffer((m_buffer_size = content_length) + 1, m_body, std::distance(m_body, m_position));
|
||||
|
||||
m_position = m_buffer.get() + std::distance(m_body, m_position);
|
||||
m_body = m_buffer.get();
|
||||
}
|
||||
}
|
||||
|
||||
if ((unsigned int)std::distance(m_buffer.get(), m_position) != m_buffer_size)
|
||||
if (m_position < m_body + m_content_length)
|
||||
return;
|
||||
|
||||
torrent::this_thread::poll()->remove_read(this);
|
||||
@@ -184,13 +147,13 @@ SCgiTask::event_read() {
|
||||
|
||||
// Clean up logging, this is just plain ugly...
|
||||
// write(m_logFd, "\n---\n", sizeof("\n---\n"));
|
||||
result = ::write(m_parent->log_fd(), m_buffer.get(), m_buffer_size);
|
||||
result = ::write(m_parent->log_fd(), m_buffer.data() + m_body, m_position - m_body);
|
||||
result = ::write(m_parent->log_fd(), "\n---\n", sizeof("\n---\n"));
|
||||
}
|
||||
|
||||
lt_log_print_dump(torrent::LOG_RPC_DUMP, m_body, m_buffer_size - std::distance(m_buffer.get(), m_body), "scgi", "RPC read.", 0);
|
||||
lt_log_print_dump(torrent::LOG_RPC_DUMP, m_buffer.data() + m_body, m_content_length, "scgi", "RPC read.", 0);
|
||||
|
||||
receive_call(m_body, m_buffer_size - std::distance(m_buffer.get(), m_body));
|
||||
receive_call(m_buffer.data() + m_body, m_content_length);
|
||||
return;
|
||||
|
||||
event_read_failed:
|
||||
@@ -200,19 +163,18 @@ event_read_failed:
|
||||
|
||||
void
|
||||
SCgiTask::event_write() {
|
||||
int bytes = ::send(m_fileDesc, m_position, m_buffer_size, MSG_NOSIGNAL);
|
||||
int bytes = ::send(m_fileDesc, m_buffer.data() + m_position, m_buffer.size() - m_position, 0);
|
||||
|
||||
if (bytes == -1) {
|
||||
if (!(errno == EAGAIN || errno == EINTR))
|
||||
if (!(errno == EAGAIN || errno == EINTR || errno == EPIPE))
|
||||
close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_position += bytes;
|
||||
m_buffer_size -= bytes;
|
||||
|
||||
if (bytes == 0 || m_buffer_size == 0)
|
||||
if (bytes == 0 || m_position == m_buffer.size())
|
||||
return close();
|
||||
}
|
||||
|
||||
@@ -221,6 +183,95 @@ SCgiTask::event_error() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool
|
||||
SCgiTask::parse_headers(const char* current, unsigned int header_length) {
|
||||
std::string content_type;
|
||||
|
||||
const char* header_end = current + header_length;
|
||||
|
||||
// Parse out the null-terminated header keys and values, with
|
||||
// checks to ensure it doesn't scan beyond the limits of the
|
||||
// header
|
||||
while (current < header_end) {
|
||||
auto* key = current;
|
||||
auto* key_end = static_cast<const char*>(std::memchr(current, '\0', header_end - current));
|
||||
|
||||
if (key_end == nullptr)
|
||||
return false;
|
||||
|
||||
current = key_end + 1;
|
||||
|
||||
if (current >= header_end)
|
||||
return false;
|
||||
|
||||
auto* value = current;
|
||||
auto* value_end = static_cast<const char*>(std::memchr(current, '\0', header_end - current));
|
||||
|
||||
if (value_end == nullptr)
|
||||
return false;
|
||||
|
||||
current = value_end + 1;
|
||||
|
||||
if (std::strncmp(key, "CONTENT_LENGTH", 14+1) == 0) {
|
||||
auto [content_pos, ec] = std::from_chars(value, value_end, m_content_length);
|
||||
|
||||
if (*content_pos != '\0' || m_content_length <= 0 || m_content_length > max_content_size)
|
||||
return false;
|
||||
|
||||
} else if (std::strncmp(key, "CONTENT_TYPE", 12+1) == 0) {
|
||||
content_type = value;
|
||||
|
||||
} else if (std::strncmp(key, "ACCEPT_ENCODING", 15+1) == 0) {
|
||||
std::string accept_encoding(value, value_end - value);
|
||||
|
||||
if (accept_encoding.find("gzip") != std::string::npos)
|
||||
m_accepts_compression = true;
|
||||
|
||||
} else if (std::strncmp(key, "UNTRUSTED_CONNECTION", 20+1) == 0) {
|
||||
if (std::strncmp(value, "1", 1+1) == 0)
|
||||
m_trusted = false;
|
||||
else if (std::strncmp(value, "0", 1+1) == 0)
|
||||
; // Default is trusted, so do nothing.
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != header_end)
|
||||
return false;
|
||||
|
||||
if (m_content_length == 0)
|
||||
return false;
|
||||
|
||||
// Move past the ',' that ends the header.
|
||||
current++;
|
||||
|
||||
if (current > m_buffer.data() + m_buffer.size())
|
||||
throw torrent::internal_error("SCgiTask::event_read() header parsing overflow : body start is beyond buffer end");
|
||||
|
||||
m_body = current - m_buffer.data();
|
||||
|
||||
if (!detect_content_type(content_type))
|
||||
return false;
|
||||
|
||||
if (m_body + m_content_length > m_buffer.size()) {
|
||||
if (m_content_length > default_buffer_size) {
|
||||
std::vector<char> tmp(m_content_length);
|
||||
|
||||
std::memcpy(tmp.data(), m_buffer.data() + m_body, m_position - m_body);
|
||||
m_buffer.swap(tmp);
|
||||
|
||||
} else {
|
||||
std::memmove(m_buffer.data(), m_buffer.data() + m_body, m_position - m_body);
|
||||
}
|
||||
|
||||
m_position = m_position - m_body;
|
||||
m_body = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
scgi_match_content_type(const std::string& content_type, const char* type) {
|
||||
std::string::size_type pos = content_type.find_first_of(" ;");
|
||||
@@ -236,7 +287,8 @@ SCgiTask::detect_content_type(const std::string& content_type) {
|
||||
if (content_type.empty()) {
|
||||
// If no CONTENT_TYPE was supplied, peek at the body to check if it's JSON
|
||||
// { is a single request object, while [ is a batch array
|
||||
if (*m_body == '{' || *m_body == '[')
|
||||
|
||||
if (m_buffer[m_body] == '{' || m_buffer[m_body] == '[')
|
||||
m_content_type = ContentType::JSON;
|
||||
else
|
||||
m_content_type = ContentType::XML;
|
||||
@@ -255,92 +307,138 @@ SCgiTask::detect_content_type(const std::string& content_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If bufferSize is zero then memcpy won't do anything.
|
||||
void
|
||||
SCgiTask::realloc_buffer(uint32_t size, const char* buffer, uint32_t bufferSize) {
|
||||
auto tmp = new char[size];
|
||||
|
||||
std::memcpy(tmp, buffer, bufferSize);
|
||||
|
||||
m_buffer.reset(tmp);
|
||||
}
|
||||
|
||||
void
|
||||
SCgiTask::receive_call(const char* buffer, uint32_t length) {
|
||||
// TODO: Rewrite RpcManager.process to pass the result buffer instead of having to copy it.
|
||||
assert(torrent::system::Thread::self() == scgi_thread::thread());
|
||||
|
||||
auto scgi_thread = torrent::utils::Thread::self();
|
||||
|
||||
auto result_callback = [this, scgi_thread](const char* b, uint32_t l) {
|
||||
receive_write(b, l);
|
||||
|
||||
scgi_thread->callback_interrupt_polling(this, [this]() {
|
||||
// Only need to lock once here as a memory barrier.
|
||||
m_result_mutex.lock();
|
||||
m_result_mutex.unlock();
|
||||
|
||||
torrent::this_thread::poll()->insert_write(this);
|
||||
});
|
||||
};
|
||||
|
||||
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||
RpcManager::RPCType rpc_type;
|
||||
|
||||
switch (content_type()) {
|
||||
case rpc::SCgiTask::ContentType::JSON:
|
||||
torrent::main_thread::thread()->callback_interrupt_polling(this, [buffer, length, result_callback]() {
|
||||
rpc.process(RpcManager::RPCType::JSON, buffer, length,
|
||||
[result_callback](const char* b, uint32_t l) {
|
||||
result_callback(b, l);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
rpc_type = RpcManager::RPCType::JSON;
|
||||
break;
|
||||
|
||||
case rpc::SCgiTask::ContentType::XML:
|
||||
torrent::main_thread::thread()->callback_interrupt_polling(this, [buffer, length, result_callback]() {
|
||||
rpc.process(RpcManager::RPCType::XML, buffer, length,
|
||||
[result_callback](const char* b, uint32_t l) {
|
||||
result_callback(b, l);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
rpc_type = RpcManager::RPCType::XML;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw torrent::internal_error("SCgiTask::receive_call(...) received bad input.");
|
||||
}
|
||||
|
||||
// TODO: Rewrite RpcManager.process to pass the result buffer instead of having to copy it.
|
||||
// TODO: Also allow us to request RpcManager.process to reserve space for a header.
|
||||
// TODO: Completely remove the mutex, and align m_buffer?
|
||||
|
||||
auto result_callback = [this](const char* b, uint32_t l) {
|
||||
receive_write(b, l);
|
||||
|
||||
// Memory barrier for the result data.
|
||||
// std::atomic_thread_fence(std::memory_order_release);
|
||||
m_result_mutex.lock();
|
||||
m_result_mutex.unlock();
|
||||
|
||||
scgi_thread::thread()->callback_interrupt_polling(this, [this]() {
|
||||
if (!is_open())
|
||||
return;
|
||||
|
||||
torrent::this_thread::poll()->insert_write(this);
|
||||
|
||||
// Memory barrier for the result data.
|
||||
// std::atomic_thread_fence(std::memory_order_acquire);
|
||||
m_result_mutex.lock();
|
||||
m_result_mutex.unlock();
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Memory barrier for the input data.
|
||||
// std::atomic_thread_fence(std::memory_order_release);
|
||||
m_result_mutex.lock();
|
||||
m_result_mutex.unlock();
|
||||
|
||||
torrent::main_thread::thread()->callback_interrupt_polling(this, [this, rpc_type, buffer, length, result_callback]() {
|
||||
// Memory barrier for the input data.
|
||||
// std::atomic_thread_fence(std::memory_order_acquire);
|
||||
m_result_mutex.lock();
|
||||
m_result_mutex.unlock();
|
||||
|
||||
if (m_trusted)
|
||||
rpc.process(rpc_type, buffer, length, result_callback);
|
||||
else
|
||||
rpc.process_untrusted(rpc_type, buffer, length, result_callback);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
SCgiTask::receive_write(const char* buffer, uint32_t length) {
|
||||
if (buffer == NULL || length > (100 << 20))
|
||||
assert(torrent::system::Thread::self() == torrent::main_thread::thread());
|
||||
|
||||
if (buffer == nullptr || length > (100 << 20))
|
||||
throw torrent::internal_error("SCgiTask::receive_write(...) received bad input.");
|
||||
|
||||
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||
|
||||
// Need to cast due to a bug in MacOSX gcc-4.0.1.
|
||||
if (length + 256 > std::max(m_buffer_size, (unsigned int)default_buffer_size))
|
||||
realloc_buffer(length + 256, NULL, 0);
|
||||
|
||||
const auto header = m_content_type == ContentType::JSON
|
||||
? "Status: 200 OK\r\nContent-Type: application/json\r\nContent-Length: %i\r\n\r\n"
|
||||
: "Status: 200 OK\r\nContent-Type: text/xml\r\nContent-Length: %i\r\n\r\n";
|
||||
|
||||
// Who ever bothers to check the return value?
|
||||
int headerSize = snprintf(m_buffer.get(), m_buffer_size, header, length);
|
||||
|
||||
m_position = m_buffer.get();
|
||||
m_buffer_size = length + headerSize;
|
||||
|
||||
std::memcpy(m_buffer.get() + headerSize, buffer, length);
|
||||
// Main thread callback already locked this mutex.
|
||||
// auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||
|
||||
// Write to log prior to possible compression
|
||||
if (m_parent->log_fd() >= 0) {
|
||||
[[maybe_unused]] int result;
|
||||
result = write(m_parent->log_fd(), m_buffer.get(), m_buffer_size);
|
||||
int result [[maybe_unused]];
|
||||
// Clean up logging, this is just plain ugly...
|
||||
// write(m_logFd, "\n---\n", sizeof("\n---\n"));
|
||||
result = write(m_parent->log_fd(), buffer, length);
|
||||
result = write(m_parent->log_fd(), "\n---\n", sizeof("\n---\n"));
|
||||
}
|
||||
|
||||
lt_log_print_dump(torrent::LOG_RPC_DUMP, m_buffer.get(), m_buffer_size, "scgi", "RPC write.", 0);
|
||||
lt_log_print_dump(torrent::LOG_RPC_DUMP, buffer, length, "scgi", "RPC write.", 0);
|
||||
|
||||
if (m_accepts_compression && rpc.scgi_allow_compression() && length > rpc.scgi_min_compress_size())
|
||||
gzip_response(buffer, length);
|
||||
else
|
||||
plaintext_response(buffer, length);
|
||||
}
|
||||
|
||||
// We don't know the size of the content-length string, so leave sufficient space for the header
|
||||
// strings plus max length of content-length.
|
||||
|
||||
void
|
||||
SCgiTask::plaintext_response(const char* buffer, uint32_t content_length) {
|
||||
auto header_first = content_type() == ContentType::XML ? header_xml : header_json;
|
||||
auto header_first_size = content_type() == ContentType::XML ? header_xml_size : header_json_size;
|
||||
auto length_str = std::to_string(content_length);
|
||||
|
||||
m_buffer.resize(header_first_size + length_str.size() + header_last_size + content_length);
|
||||
|
||||
std::memcpy(m_buffer.data(), header_first, header_first_size);
|
||||
std::memcpy(m_buffer.data() + header_first_size, length_str.data(), length_str.size());
|
||||
std::memcpy(m_buffer.data() + header_first_size + length_str.size(), header_last, header_last_size);
|
||||
std::memcpy(m_buffer.data() + header_first_size + length_str.size() + header_last_size, buffer, content_length);
|
||||
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
void
|
||||
SCgiTask::gzip_response(const char* buffer, uint32_t content_length) {
|
||||
auto header_first = content_type() == ContentType::XML ? header_xml : header_json;
|
||||
auto header_first_size = content_type() == ContentType::XML ? header_xml_size : header_json_size;
|
||||
|
||||
unsigned int body_offset = header_first_size + 20 + header_last_size;
|
||||
|
||||
utils::gzip_compress_to_vector(buffer, content_length, m_buffer, body_offset);
|
||||
|
||||
auto length_str = std::to_string(m_buffer.size() - body_offset);
|
||||
auto header_size = header_first_size + length_str.size() + header_last_size;
|
||||
auto header_start = body_offset - header_size;
|
||||
|
||||
if (header_size > body_offset)
|
||||
throw torrent::internal_error("SCgiTask::gzip_response(...) header overflow : start position is negative");
|
||||
|
||||
if (header_start > body_offset)
|
||||
throw torrent::internal_error("SCgiTask::gzip_response(...) header overflow : start position is beyond buffer start");
|
||||
|
||||
std::memcpy(m_buffer.data() + header_start, header_first, header_first_size);
|
||||
std::memcpy(m_buffer.data() + header_start + header_first_size, length_str.data(), length_str.size());
|
||||
std::memcpy(m_buffer.data() + header_start + header_first_size + length_str.size(), header_last, header_last_size);
|
||||
|
||||
m_position = header_start;
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
+23
-9
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <torrent/event.h>
|
||||
|
||||
namespace rpc {
|
||||
@@ -11,9 +12,9 @@ class SCgi;
|
||||
|
||||
class SCgiTask : public torrent::Event {
|
||||
public:
|
||||
static const unsigned int default_buffer_size = 2047;
|
||||
static const int max_header_size = 2000;
|
||||
static const int max_content_size = (2 << 23);
|
||||
static constexpr int default_buffer_size = 8191;
|
||||
static constexpr int max_header_size = 2000;
|
||||
static constexpr int max_content_size = (2 << 23);
|
||||
|
||||
enum ContentType { XML, JSON };
|
||||
|
||||
@@ -36,23 +37,36 @@ public:
|
||||
void event_error() override;
|
||||
|
||||
private:
|
||||
static constexpr char header_xml[] = "Status: 200 OK\r\nContent-Type: text/xml\r\nContent-Length: ";
|
||||
static constexpr char header_json[] = "Status: 200 OK\r\nContent-Type: application/json\r\nContent-Length: ";
|
||||
static constexpr char header_last[] = "\r\n\r\n";
|
||||
|
||||
static constexpr size_t header_xml_size = sizeof(header_xml) - 1;
|
||||
static constexpr size_t header_json_size = sizeof(header_json) - 1;
|
||||
static constexpr size_t header_last_size = sizeof(header_last) - 1;
|
||||
|
||||
bool parse_headers(const char* current, unsigned int header_length);
|
||||
bool detect_content_type(const std::string& content_type);
|
||||
void realloc_buffer(uint32_t size, const char* buffer, uint32_t bufferSize);
|
||||
|
||||
void receive_call(const char* buffer, uint32_t length);
|
||||
void receive_write(const char* buffer, uint32_t length);
|
||||
|
||||
void plaintext_response(const char* buffer, uint32_t content_length);
|
||||
void gzip_response(const char* buffer, uint32_t content_length);
|
||||
|
||||
SCgi* m_parent{};
|
||||
|
||||
std::mutex m_result_mutex;
|
||||
|
||||
std::unique_ptr<char[]> m_buffer;
|
||||
char* m_position{};
|
||||
char* m_body{};
|
||||
std::vector<char> m_buffer;
|
||||
unsigned int m_position{};
|
||||
unsigned int m_body{};
|
||||
|
||||
unsigned int m_buffer_size{0};
|
||||
unsigned int m_content_length{};
|
||||
ContentType m_content_type{XML};
|
||||
|
||||
ContentType m_content_type{ XML };
|
||||
bool m_accepts_compression{};
|
||||
bool m_trusted{true};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -385,6 +385,10 @@ xmlrpc_call_command(xmlrpc_env* env, xmlrpc_value* args, void* voidServerInfo) {
|
||||
|
||||
return object_to_xmlrpc(env, rpc::commands.call_command(itr, object, target));
|
||||
|
||||
} catch (untrusted_error& e) {
|
||||
xmlrpc_env_set_fault(env, XMLRPC_REQUEST_REFUSED_ERROR, e.what());
|
||||
return NULL;
|
||||
|
||||
} catch (xmlrpc_error_c& e) {
|
||||
xmlrpc_env_set_fault(env, e.type(), e.what());
|
||||
return NULL;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/object.h>
|
||||
|
||||
@@ -31,7 +30,7 @@ const int XMLRPC_PARSE_ERROR = -503;
|
||||
// const int XMLRPC_NETWORK_ERROR = -504;
|
||||
// const int XMLRPC_TIMEOUT_ERROR = -505;
|
||||
const int XMLRPC_NO_SUCH_METHOD_ERROR = -506;
|
||||
// const int XMLRPC_REQUEST_REFUSED_ERROR = -507;
|
||||
const int XMLRPC_REQUEST_REFUSED_ERROR = -507;
|
||||
// const int XMLRPC_INTROSPECTION_DISABLED_ERROR = -508;
|
||||
const int XMLRPC_LIMIT_EXCEEDED_ERROR = -509;
|
||||
// const int XMLRPC_INVALID_UTF8_ERROR = -510;
|
||||
@@ -238,7 +237,11 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "invalid parameters: too few");
|
||||
}
|
||||
|
||||
return rpc::commands.call_command(cmd_itr, params_raw, target);
|
||||
try {
|
||||
return rpc::commands.call_command(cmd_itr, params_raw, target);
|
||||
} catch (untrusted_error& e) {
|
||||
throw rpc_error(XMLRPC_REQUEST_REFUSED_ERROR, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -127,8 +127,8 @@ ThreadScgi::next_timeout() {
|
||||
|
||||
namespace scgi_thread {
|
||||
|
||||
torrent::utils::Thread* thread() { return scgi::ThreadScgiInternal::thread_scgi(); }
|
||||
std::thread::id thread_id() { return scgi::ThreadScgiInternal::thread_scgi()->thread_id(); }
|
||||
torrent::system::Thread* thread() { return scgi::ThreadScgiInternal::thread_scgi(); }
|
||||
std::thread::id thread_id() { return scgi::ThreadScgiInternal::thread_scgi()->thread_id(); }
|
||||
|
||||
void callback(void* target, std::function<void ()>&& fn) { scgi::ThreadScgiInternal::thread_scgi()->callback(target, std::move(fn)); }
|
||||
void cancel_callback(void* target) { scgi::ThreadScgiInternal::thread_scgi()->cancel_callback(target); }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
namespace rpc {
|
||||
class SCgi;
|
||||
@@ -13,7 +13,7 @@ namespace scgi {
|
||||
|
||||
class ThreadScgiInternal;
|
||||
|
||||
class ThreadScgi : public torrent::utils::Thread {
|
||||
class ThreadScgi : public torrent::system::Thread {
|
||||
public:
|
||||
|
||||
static void create_thread();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <torrent/object_stream.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/resume.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "core/download.h"
|
||||
@@ -74,15 +75,13 @@ DownloadStorer::build_streams(bool skip_static) {
|
||||
|
||||
std::string
|
||||
DownloadStorer::build_path(const std::string& session_path) {
|
||||
auto info_hash = m_download->info()->info_hash();
|
||||
|
||||
if (session_path.empty())
|
||||
throw torrent::internal_error("DownloadStorer::build_path() called with empty session path.");
|
||||
|
||||
if (session_path.back() != '/')
|
||||
throw torrent::internal_error("DownloadStorer::build_path() session path missing trailing slash.");
|
||||
|
||||
return session_path + torrent::hash_string_to_hex_str(info_hash) + ".torrent";
|
||||
return session_path + torrent::utils::transform_to_hex_str(m_download->info()->info_hash()) + ".torrent";
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+113
-51
@@ -14,9 +14,14 @@
|
||||
#define LT_LOG(log_fmt, ...) \
|
||||
lt_log_print(torrent::LOG_SESSION_EVENTS, "session-events: " log_fmt, __VA_ARGS__);
|
||||
|
||||
// TODO: Properly handle errors.
|
||||
//
|
||||
// TODO: If no more sockets can be opened, wait for a job to finish. if all is finished, use a
|
||||
// timeout and try again.
|
||||
|
||||
namespace session {
|
||||
|
||||
SessionManager::SessionManager(torrent::utils::Thread* thread)
|
||||
SessionManager::SessionManager(torrent::system::Thread* thread)
|
||||
: m_thread(thread),
|
||||
m_lockfile(std::make_unique<utils::Lockfile>()) {
|
||||
}
|
||||
@@ -68,19 +73,20 @@ SessionManager::save_resume_download(core::Download* download) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_pending_builds_mutex);
|
||||
|
||||
// TODO: This is under the wrong lock.
|
||||
if (!m_active)
|
||||
throw torrent::internal_error("SessionManager::save_resume_download() called while not active.");
|
||||
|
||||
if (std::find(m_pending_builds.begin(), m_pending_builds.end(), download) != m_pending_builds.end()) {
|
||||
// TODO: Do we need to make sure we're getting processed?
|
||||
LT_LOG("download already in pending build of resume save : download:%p", download);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pending_builds.empty())
|
||||
torrent::main_thread::callback(this, [this]() { process_pending_resume_builds(false); });
|
||||
|
||||
m_pending_builds.push_back(download);
|
||||
|
||||
callback_pending_builds();
|
||||
|
||||
LT_LOG("build of resume save data queued : download:%p", download);
|
||||
}
|
||||
}
|
||||
@@ -112,8 +118,10 @@ SessionManager::save_full_download(core::Download* download) {
|
||||
if (!m_active)
|
||||
throw torrent::internal_error("SessionManager::save_download() called while not active.");
|
||||
|
||||
if (replace_save_request_unsafe(save_request))
|
||||
throw torrent::internal_error("SessionManager::save_full_download() replacing existing save request, not supported?");
|
||||
if (replace_save_request_unsafe(save_request)) {
|
||||
LT_LOG("updated pending save request with full save data : download:%p", download);
|
||||
return;
|
||||
}
|
||||
|
||||
m_save_requests.push_back(std::move(save_request));
|
||||
m_save_request_counter = m_save_requests.size();
|
||||
@@ -121,8 +129,7 @@ SessionManager::save_full_download(core::Download* download) {
|
||||
LT_LOG("queued new full save request : download:%p", download);
|
||||
}
|
||||
|
||||
if (!m_processing_saves_callback_scheduled.exchange(true))
|
||||
session_thread::callback(this, [this]() { process_save_request(); });
|
||||
callback_save_request();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -219,7 +226,40 @@ SessionManager::cleanup() {
|
||||
}
|
||||
|
||||
void
|
||||
SessionManager::process_pending_resume_builds(bool is_flushing) {
|
||||
SessionManager::callback_pending_builds() {
|
||||
if (m_save_request_counter >= max_concurrent_requests)
|
||||
return;
|
||||
|
||||
if (m_callback_scheduled_process_pending_builds.exchange(true))
|
||||
return;
|
||||
|
||||
torrent::main_thread::callback(this, [this]() { process_pending_builds(false); });
|
||||
}
|
||||
|
||||
void
|
||||
SessionManager::callback_save_request() {
|
||||
if (m_save_request_counter == 0)
|
||||
return;
|
||||
|
||||
if (m_processing_save_counter >= max_concurrent_processing)
|
||||
return;
|
||||
|
||||
if (m_callback_scheduled_process_saves_request.exchange(true))
|
||||
return;
|
||||
|
||||
session_thread::callback(this, [this]() { process_save_request(); });
|
||||
}
|
||||
|
||||
void
|
||||
SessionManager::callback_finished_saves() {
|
||||
if (m_callback_scheduled_process_finished_saves.exchange(true))
|
||||
return;
|
||||
|
||||
session_thread::callback(this, [this]() { process_finished_saves(); });
|
||||
}
|
||||
|
||||
void
|
||||
SessionManager::process_pending_builds(bool is_flushing) {
|
||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||
|
||||
std::vector<SaveRequest> requests;
|
||||
@@ -229,6 +269,8 @@ SessionManager::process_pending_resume_builds(bool is_flushing) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_pending_builds_mutex);
|
||||
|
||||
m_callback_scheduled_process_pending_builds = false;
|
||||
|
||||
while (!m_pending_builds.empty()) {
|
||||
if (!is_flushing && m_save_request_counter + requests.size() >= max_concurrent_requests)
|
||||
break;
|
||||
@@ -270,15 +312,14 @@ SessionManager::process_pending_resume_builds(bool is_flushing) {
|
||||
}
|
||||
|
||||
m_save_requests.push_back(std::move(save_request));
|
||||
m_save_request_counter = m_save_requests.size();
|
||||
|
||||
LT_LOG("queued new resume save request : download:%p", save_request.download);
|
||||
}
|
||||
|
||||
m_save_request_counter = m_save_requests.size();
|
||||
}
|
||||
|
||||
if (!is_flushing && !m_processing_saves_callback_scheduled.exchange(true))
|
||||
session_thread::callback(this, [this]() { process_save_request_with_pending_callback(); });
|
||||
if (!is_flushing)
|
||||
callback_save_request();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -288,25 +329,23 @@ SessionManager::process_save_request() {
|
||||
if (m_path.empty())
|
||||
throw torrent::internal_error("SessionManager::process_save_request() called with empty path.");
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
if (!m_active)
|
||||
throw torrent::internal_error("SessionManager::process_save_request() called while not active.");
|
||||
m_callback_scheduled_process_saves_request = false;
|
||||
|
||||
while (!m_save_requests.empty() && m_processing_saves.size() < max_concurrent_requests)
|
||||
process_next_save_request_unsafe();
|
||||
if (!m_active)
|
||||
throw torrent::internal_error("SessionManager::process_save_request() called while not active.");
|
||||
|
||||
m_processing_saves_callback_scheduled = false;
|
||||
}
|
||||
while (!m_save_requests.empty()) {
|
||||
if (m_processing_saves.size() >= max_concurrent_processing)
|
||||
break;
|
||||
|
||||
void
|
||||
SessionManager::process_save_request_with_pending_callback() {
|
||||
process_save_request();
|
||||
process_next_save_request_unsafe();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_pending_builds_mutex);
|
||||
|
||||
if (!m_pending_builds.empty())
|
||||
torrent::main_thread::callback(this, [this]() { process_pending_resume_builds(false); });
|
||||
callback_pending_builds();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -317,19 +356,20 @@ SessionManager::process_next_save_request_unsafe() {
|
||||
m_save_request_counter = m_save_requests.size();
|
||||
|
||||
auto itr = m_processing_saves.insert(m_processing_saves.end(), ProcessingSave{});
|
||||
m_processing_save_counter = m_processing_saves.size();
|
||||
|
||||
itr->second = std::move(request);
|
||||
itr->first = std::async(std::launch::async, [this, itr]() {
|
||||
auto cleanup_fn = [this, itr]() {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_finished_saves.empty())
|
||||
session_thread::callback(this, [this]() { process_finished_saves(); });
|
||||
callback_finished_saves();
|
||||
|
||||
m_finished_saves.push_back(std::move(*itr));
|
||||
m_finished_condition.notify_all();
|
||||
|
||||
m_processing_saves.erase(itr);
|
||||
m_processing_save_counter = m_processing_saves.size();
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -344,6 +384,8 @@ SessionManager::process_next_save_request_unsafe() {
|
||||
|
||||
cleanup_fn();
|
||||
});
|
||||
|
||||
LT_LOG("started save of download : download:%p path:%s", itr->second.download, itr->second.path.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -352,6 +394,8 @@ SessionManager::process_finished_saves() {
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
m_callback_scheduled_process_finished_saves = false;
|
||||
|
||||
if (!m_active)
|
||||
throw torrent::internal_error("SessionManager::process_finished_saves() called while not active.");
|
||||
|
||||
@@ -371,6 +415,7 @@ SessionManager::process_finished_saves() {
|
||||
|
||||
m_last_storage_error_message = torrent::this_thread::cached_time();
|
||||
m_ignored_storage_error_count = 0;
|
||||
|
||||
continue;
|
||||
|
||||
} catch (torrent::internal_error& e) {
|
||||
@@ -386,14 +431,18 @@ SessionManager::process_finished_saves() {
|
||||
}
|
||||
|
||||
m_finished_saves.clear();
|
||||
|
||||
callback_save_request();
|
||||
}
|
||||
|
||||
void
|
||||
SessionManager::flush_all_and_wait_unsafe(std::unique_lock<std::mutex>& lock) {
|
||||
LT_LOG("flushing all pending saves", 0);
|
||||
|
||||
// Caller already ensured pending builds are empty.
|
||||
|
||||
while (!m_save_requests.empty()) {
|
||||
if (m_processing_saves.size() >= max_cleanup_processing) {
|
||||
if (m_processing_save_counter >= max_cleanup_processing) {
|
||||
m_finished_condition.wait(lock);
|
||||
continue;
|
||||
}
|
||||
@@ -426,11 +475,25 @@ SessionManager::replace_save_request_unsafe(SaveRequest& save_request) {
|
||||
if (itr->path != save_request.path)
|
||||
throw torrent::internal_error("SessionManager::replace_save_request_unsafe() path mismatch on replace: " + itr->path + " != " + save_request.path);
|
||||
|
||||
if (save_request.torrent_stream != nullptr)
|
||||
throw torrent::internal_error("SessionManager::replace_save_request_unsafe() cannot replace full save requests.");
|
||||
// If the existing request is a full save (created during torrent initialization),
|
||||
// keep its torrent_stream but update the resume streams with newer data.
|
||||
//
|
||||
// Otherwise, replace all streams.
|
||||
|
||||
itr->rtorrent_stream = std::move(save_request.rtorrent_stream);
|
||||
itr->libtorrent_stream = std::move(save_request.libtorrent_stream);
|
||||
if (itr->torrent_stream != nullptr) {
|
||||
// Keep existing full-save torrent stream, only update resume streams
|
||||
if (save_request.rtorrent_stream != nullptr)
|
||||
itr->rtorrent_stream = std::move(save_request.rtorrent_stream);
|
||||
|
||||
if (save_request.libtorrent_stream != nullptr)
|
||||
itr->libtorrent_stream = std::move(save_request.libtorrent_stream);
|
||||
|
||||
} else {
|
||||
// No full save pending, replace everything
|
||||
itr->torrent_stream = std::move(save_request.torrent_stream);
|
||||
itr->rtorrent_stream = std::move(save_request.rtorrent_stream);
|
||||
itr->libtorrent_stream = std::move(save_request.libtorrent_stream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -439,18 +502,6 @@ bool
|
||||
SessionManager::remove_completely_unsafe(core::Download* download, std::unique_lock<std::mutex>& lock) {
|
||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||
|
||||
auto remove_requests = [this, download]() {
|
||||
auto itr = std::remove_if(m_save_requests.begin(), m_save_requests.end(), [download](auto& req) {
|
||||
return req.download == download;
|
||||
});
|
||||
|
||||
if (itr == m_save_requests.end())
|
||||
return false;
|
||||
|
||||
m_save_requests.erase(itr, m_save_requests.end());
|
||||
return true;
|
||||
};
|
||||
|
||||
auto remove_pending = [this, download]() {
|
||||
std::unique_lock<std::mutex> pending_lock(m_pending_builds_mutex);
|
||||
|
||||
@@ -465,13 +516,28 @@ SessionManager::remove_completely_unsafe(core::Download* download, std::unique_l
|
||||
return true;
|
||||
};
|
||||
|
||||
auto remove_requests = [this, download]() {
|
||||
auto itr = std::remove_if(m_save_requests.begin(), m_save_requests.end(), [download](auto& req) {
|
||||
return req.download == download;
|
||||
});
|
||||
|
||||
if (itr == m_save_requests.end())
|
||||
return false;
|
||||
|
||||
m_save_requests.erase(itr, m_save_requests.end());
|
||||
m_save_request_counter = m_save_requests.size();
|
||||
return true;
|
||||
};
|
||||
|
||||
bool removed_something = false;
|
||||
|
||||
if (remove_pending())
|
||||
removed_something = true;
|
||||
|
||||
if (remove_requests())
|
||||
if (remove_requests()) {
|
||||
removed_something = true;
|
||||
callback_pending_builds();
|
||||
}
|
||||
|
||||
// This may block for a relatively long time if fdatasync is in use, however this is necessary.
|
||||
while (true) {
|
||||
@@ -494,9 +560,5 @@ SessionManager::remove_completely_unsafe(core::Download* download, std::unique_l
|
||||
return removed_something;
|
||||
}
|
||||
|
||||
// TODO: Properly handle errors.
|
||||
// TODO: If no more sockets can be opened, wait for a job to finish. if all is finished, use a
|
||||
// timeout and try again.
|
||||
|
||||
} // namespace session
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
constexpr static int max_concurrent_processing = 16;
|
||||
constexpr static int max_cleanup_processing = 64;
|
||||
|
||||
SessionManager(torrent::utils::Thread* thread);
|
||||
SessionManager(torrent::system::Thread* thread);
|
||||
~SessionManager();
|
||||
|
||||
bool is_used() const;
|
||||
@@ -74,9 +74,12 @@ protected:
|
||||
void flush_all_pending_builds();
|
||||
|
||||
private:
|
||||
void process_pending_resume_builds(bool is_flushing);
|
||||
void callback_pending_builds();
|
||||
void callback_save_request();
|
||||
void callback_finished_saves();
|
||||
|
||||
void process_pending_builds(bool is_flushing);
|
||||
void process_save_request();
|
||||
void process_save_request_with_pending_callback();
|
||||
void process_next_save_request_unsafe();
|
||||
void process_finished_saves();
|
||||
|
||||
@@ -86,7 +89,7 @@ private:
|
||||
bool replace_save_request_unsafe(SaveRequest& download);
|
||||
bool remove_completely_unsafe(core::Download* download, std::unique_lock<std::mutex>& lock);
|
||||
|
||||
torrent::utils::Thread* m_thread;
|
||||
torrent::system::Thread* m_thread;
|
||||
|
||||
bool m_freeze_info{};
|
||||
std::string m_path;
|
||||
@@ -101,10 +104,14 @@ private:
|
||||
std::deque<SaveRequest> m_save_requests;
|
||||
std::atomic<size_t> m_save_request_counter{};
|
||||
std::list<ProcessingSave> m_processing_saves;
|
||||
std::atomic<bool> m_processing_saves_callback_scheduled{};
|
||||
std::atomic<size_t> m_processing_save_counter{};
|
||||
std::condition_variable m_finished_condition;
|
||||
std::vector<ProcessingSave> m_finished_saves;
|
||||
|
||||
std::atomic<bool> m_callback_scheduled_process_pending_builds{};
|
||||
std::atomic<bool> m_callback_scheduled_process_saves_request{};
|
||||
std::atomic<bool> m_callback_scheduled_process_finished_saves{};
|
||||
|
||||
std::unique_ptr<utils::Lockfile> m_lockfile;
|
||||
|
||||
std::chrono::microseconds m_last_storage_error_message{};
|
||||
@@ -119,7 +126,7 @@ inline bool SessionManager::is_used() const { return !m_path.e
|
||||
inline std::string SessionManager::path() const { return m_path; }
|
||||
inline bool SessionManager::use_fsyncdisk() const { return true; }
|
||||
inline bool SessionManager::use_lock() const { return m_use_lock; }
|
||||
inline void SessionManager::flush_all_pending_builds() { process_pending_resume_builds(true); }
|
||||
inline void SessionManager::flush_all_pending_builds() { process_pending_builds(true); }
|
||||
|
||||
} // namespace session
|
||||
|
||||
|
||||
@@ -74,8 +74,8 @@ ThreadSession::next_timeout() {
|
||||
|
||||
namespace session_thread {
|
||||
|
||||
torrent::utils::Thread* thread() { return session::ThreadSessionInternal::thread_session(); }
|
||||
std::thread::id thread_id() { return session::ThreadSessionInternal::thread_session()->thread_id(); }
|
||||
torrent::system::Thread* thread() { return session::ThreadSessionInternal::thread_session(); }
|
||||
std::thread::id thread_id() { return session::ThreadSessionInternal::thread_session()->thread_id(); }
|
||||
|
||||
void callback(void* target, std::function<void ()>&& fn) { session::ThreadSessionInternal::thread_session()->callback(target, std::move(fn)); }
|
||||
void cancel_callback(void* target) { session::ThreadSessionInternal::thread_session()->cancel_callback(target); }
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef RTORRENT_SESSION_THREAD_SESSION_H
|
||||
#define RTORRENT_SESSION_THREAD_SESSION_H
|
||||
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
namespace session {
|
||||
|
||||
class SessionManager;
|
||||
class ThreadSessionInternal;
|
||||
|
||||
class ThreadSession : public torrent::utils::Thread {
|
||||
class ThreadSession : public torrent::system::Thread {
|
||||
public:
|
||||
|
||||
static void create_thread();
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "setup.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/option_strings.h>
|
||||
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "option_parser.h"
|
||||
#include "core/download_factory.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "session/download_storer.h"
|
||||
#include "utils/directory.h"
|
||||
|
||||
void do_panic(int signum);
|
||||
void print_help();
|
||||
|
||||
//
|
||||
// Parse command line args and config files:
|
||||
//
|
||||
|
||||
int
|
||||
parse_main_options(int argc, char** argv) {
|
||||
try {
|
||||
OptionParser optionParser;
|
||||
|
||||
// Converted.
|
||||
optionParser.insert_flag('h', [](auto&) { print_help(); });
|
||||
optionParser.insert_flag('n', [](auto&) { });
|
||||
optionParser.insert_flag('D', [](auto&) { });
|
||||
optionParser.insert_flag('I', [](auto&) { });
|
||||
optionParser.insert_flag('K', [](auto&) { });
|
||||
|
||||
optionParser.insert_option('b', [](auto& arg) { rpc::call_command_set_string("network.bind_address.set", arg); });
|
||||
optionParser.insert_option('d', [](auto& arg) { rpc::call_command_set_string("directory.default.set", arg); });
|
||||
optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("ip", arg); });
|
||||
optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.port_range.set", arg); });
|
||||
optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); });
|
||||
|
||||
optionParser.insert_option('O', [](auto& arg) { rpc::parse_command_single_std(arg); });
|
||||
optionParser.insert_option_list('o', [](auto& arg1, auto& arg2) { rpc::call_command_set_std_string(arg1, arg2); });
|
||||
|
||||
return optionParser.process(argc, argv);
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Failed to parse command line option: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn) {
|
||||
if (OptionParser::has_flag('n', argc, argv))
|
||||
return parse_fn("");
|
||||
|
||||
char* config_dir = std::getenv("XDG_CONFIG_HOME");
|
||||
char* home_dir = std::getenv("HOME");
|
||||
|
||||
if (config_dir != nullptr && config_dir[0] == '/' && access((std::string(config_dir) + "/rtorrent/rtorrent.rc").c_str(), F_OK) != -1)
|
||||
return parse_fn(std::string(config_dir) + "/rtorrent/rtorrent.rc");
|
||||
|
||||
if (home_dir == nullptr)
|
||||
throw torrent::input_error("Could not find home directory, HOME environment variable not set.");
|
||||
|
||||
if (access((std::string(home_dir) + "/.config/rtorrent/rtorrent.rc").c_str(), F_OK) != -1)
|
||||
return parse_fn(std::string(home_dir) + "/.config/rtorrent/rtorrent.rc");
|
||||
|
||||
return parse_fn(std::string(home_dir) + "/.rtorrent.rc");
|
||||
}
|
||||
|
||||
void
|
||||
config_comment_log(const std::string& command, const std::string& raw_args) {
|
||||
std::vector<std::string> args;
|
||||
size_t pos{};
|
||||
|
||||
while (pos < raw_args.size()) {
|
||||
size_t next_pos = raw_args.find(',', pos);
|
||||
|
||||
if (next_pos == std::string::npos)
|
||||
next_pos = raw_args.size();
|
||||
|
||||
args.push_back(raw_args.substr(pos, next_pos - pos));
|
||||
pos = next_pos + 1;
|
||||
}
|
||||
|
||||
if (command == "log.add_output")
|
||||
log_add_group_output_str(args[0], args[1]);
|
||||
else if (command == "log.open_file")
|
||||
apply_log_open_str(0, args);
|
||||
else if (command == "log.open_file.flush")
|
||||
apply_log_open_str(log_flag_flush, args);
|
||||
else if (command == "log.open_gz_file")
|
||||
apply_log_open_str(log_flag_use_gz, args);
|
||||
else if (command == "log.open_file_pid")
|
||||
apply_log_open_str(log_flag_append_pid, args);
|
||||
else if (command == "log.open_gz_file_pid")
|
||||
apply_log_open_str(log_flag_append_pid | log_flag_use_gz, args);
|
||||
else if (command == "log.append_file")
|
||||
apply_log_open_str(log_flag_append_file, args);
|
||||
else if (command == "log.append_file.flush")
|
||||
apply_log_open_str(log_flag_append_file | log_flag_flush, args);
|
||||
else if (command == "log.append_gz_file")
|
||||
apply_log_open_str(log_flag_append_file | log_flag_use_gz, args);
|
||||
else
|
||||
throw torrent::input_error("Unknown log command: " + command);
|
||||
}
|
||||
|
||||
// Call special commands in the format "# do:command=args" in the config file.
|
||||
void
|
||||
parse_config_file_comments(const std::string& path) {
|
||||
std::fstream file(path, std::ios::in);
|
||||
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
std::string line;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
if (line.size() <= 5 || line.compare(0, 5, "# do:") != 0)
|
||||
continue;
|
||||
|
||||
auto equal_pos = line.find('=');
|
||||
|
||||
if (equal_pos == std::string::npos)
|
||||
throw torrent::input_error("Invalid command in config file comment: " + line);
|
||||
|
||||
std::string command = line.substr(5, equal_pos - 5);
|
||||
std::string args = line.substr(equal_pos + 1);
|
||||
|
||||
if (command.empty())
|
||||
throw torrent::input_error("Invalid command in config file comment: " + line);
|
||||
|
||||
if (command.compare(0, 4, "log.") == 0)
|
||||
config_comment_log(command, args);
|
||||
else
|
||||
throw torrent::input_error("Unknown command in config file comment: " + line);
|
||||
|
||||
lt_log_print(torrent::LOG_NOTICE, "Pre-config command: %s=%s", command.c_str(), args.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Torrents:
|
||||
//
|
||||
|
||||
void
|
||||
load_session_torrents(const std::string& path) {
|
||||
auto entries = session::DownloadStorer::get_formated_entries(path);
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
// We don't really support session torrents that are links. These
|
||||
// would be overwritten anyway on exit, and thus not really be
|
||||
// useful.
|
||||
if (!entry.is_file())
|
||||
continue;
|
||||
|
||||
auto* f = new core::DownloadFactory(control->core());
|
||||
|
||||
f->set_session(true);
|
||||
f->set_init_load(true);
|
||||
f->slot_finished([f](){ delete f; });
|
||||
f->load(entries.path() + entry.s_name);
|
||||
f->commit();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
load_arg_torrents(char** first, char** last) {
|
||||
for (; first != last; ++first) {
|
||||
auto* f = new core::DownloadFactory(control->core());
|
||||
|
||||
f->set_start(true);
|
||||
f->set_init_load(true);
|
||||
f->slot_finished([f](){ delete f; });
|
||||
f->load(*first);
|
||||
f->commit();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Logging:
|
||||
//
|
||||
|
||||
void
|
||||
log_add_group_output_str(const std::string& group_name, const std::string& output_id) {
|
||||
int log_group = torrent::option_find_string(torrent::OPTION_LOG_GROUP, group_name.c_str());
|
||||
|
||||
torrent::log_add_group_output(log_group, output_id.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
apply_log_open_str(int output_flags, const std::vector<std::string>& args) {
|
||||
if (args.size() < 2)
|
||||
throw torrent::input_error("Invalid number of arguments.");
|
||||
|
||||
auto output_id = args[0];
|
||||
auto file_name = expand_path(args[1]);
|
||||
|
||||
if ((output_flags & log_flag_append_pid)) {
|
||||
char buffer[32];
|
||||
snprintf(buffer, 32, ".%li", (long)getpid());
|
||||
|
||||
file_name += buffer;
|
||||
}
|
||||
|
||||
bool append = (output_flags & log_flag_append_file);
|
||||
bool flush = (output_flags & log_flag_flush);
|
||||
|
||||
if ((output_flags & log_flag_use_gz))
|
||||
torrent::log_open_gz_file_output(output_id.c_str(), file_name.c_str(), append);
|
||||
else
|
||||
torrent::log_open_file_output(output_id.c_str(), file_name.c_str(), append, flush);
|
||||
|
||||
for (size_t i = 2; i < args.size(); i++)
|
||||
log_add_group_output_str(args[i], output_id);
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#ifndef RTORRENT_SETUP_H
|
||||
#define RTORRENT_SETUP_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
int parse_main_options(int argc, char** argv);
|
||||
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
|
||||
void parse_config_file_comments(const std::string& path);
|
||||
|
||||
void load_session_torrents(const std::string& path);
|
||||
void load_arg_torrents(char** first, char** last);
|
||||
|
||||
static constexpr int log_flag_use_gz = 0x1;
|
||||
static constexpr int log_flag_append_pid = 0x2;
|
||||
static constexpr int log_flag_append_file = 0x4;
|
||||
static constexpr int log_flag_flush = 0x8;
|
||||
|
||||
void log_add_group_output_str(const std::string& group_name, const std::string& output_id);
|
||||
void apply_log_open_str(int output_flags, const std::vector<std::string>& args);
|
||||
|
||||
#endif
|
||||
@@ -17,7 +17,7 @@ SignalHandler::slot_void SignalHandler::m_handlers[HIGHEST_SIGNAL];
|
||||
|
||||
void
|
||||
SignalHandler::set_default(unsigned int signum) {
|
||||
if (signum > HIGHEST_SIGNAL)
|
||||
if (signum >= HIGHEST_SIGNAL)
|
||||
throw std::logic_error("SignalHandler::set_default(...) received invalid signal value.");
|
||||
|
||||
signal(signum, SIG_DFL);
|
||||
@@ -26,7 +26,7 @@ SignalHandler::set_default(unsigned int signum) {
|
||||
|
||||
void
|
||||
SignalHandler::set_ignore(unsigned int signum) {
|
||||
if (signum > HIGHEST_SIGNAL)
|
||||
if (signum >= HIGHEST_SIGNAL)
|
||||
throw std::logic_error("SignalHandler::set_ignore(...) received invalid signal value.");
|
||||
|
||||
signal(signum, SIG_IGN);
|
||||
@@ -35,7 +35,7 @@ SignalHandler::set_ignore(unsigned int signum) {
|
||||
|
||||
void
|
||||
SignalHandler::set_handler(unsigned int signum, slot_void slot) {
|
||||
if (signum > HIGHEST_SIGNAL)
|
||||
if (signum >= HIGHEST_SIGNAL)
|
||||
throw std::logic_error("SignalHandler::set_handler(...) received invalid signal value.");
|
||||
|
||||
if (!slot)
|
||||
@@ -54,7 +54,7 @@ SignalHandler::set_handler(unsigned int signum, slot_void slot) {
|
||||
|
||||
void
|
||||
SignalHandler::set_sigaction_handler(unsigned int signum, handler_slot slot) {
|
||||
if (signum > HIGHEST_SIGNAL)
|
||||
if (signum >= HIGHEST_SIGNAL)
|
||||
throw std::logic_error("SignalHandler::set_handler(...) received invalid signal value.");
|
||||
|
||||
struct sigaction sa;
|
||||
@@ -69,7 +69,7 @@ SignalHandler::set_sigaction_handler(unsigned int signum, handler_slot slot) {
|
||||
|
||||
void
|
||||
SignalHandler::caught(int signum) {
|
||||
if ((unsigned)signum > HIGHEST_SIGNAL)
|
||||
if ((unsigned)signum >= HIGHEST_SIGNAL)
|
||||
throw std::logic_error("SignalHandler::caught(...) received invalid signal from the kernel, bork bork bork.");
|
||||
|
||||
if (!m_handlers[signum])
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/chunk_manager.h>
|
||||
#include <torrent/connection_manager.h>
|
||||
|
||||
+13
-17
@@ -1,35 +1,31 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "ui/download_list.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "control.h"
|
||||
#include "core/download.h"
|
||||
#include "core/download_list.h"
|
||||
#include "core/manager.h"
|
||||
#include "core/view.h"
|
||||
#include "core/view_manager.h"
|
||||
|
||||
#include "display/window_log.h"
|
||||
#include "display/window_title.h"
|
||||
#include "input/bindings.h"
|
||||
#include "input/manager.h"
|
||||
#include "input/path_input.h"
|
||||
|
||||
#include "display/window_log.h"
|
||||
#include "display/window_title.h"
|
||||
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
#include "control.h"
|
||||
#include "download.h"
|
||||
#include "download_list.h"
|
||||
#include "element_download_list.h"
|
||||
#include "element_log_complete.h"
|
||||
#include "element_string_list.h"
|
||||
#include "root.h"
|
||||
#include "ui/download.h"
|
||||
#include "ui/element_download_list.h"
|
||||
#include "ui/element_log_complete.h"
|
||||
#include "ui/element_string_list.h"
|
||||
#include "ui/root.h"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -231,7 +227,7 @@ DownloadList::receive_view_input(Input type) {
|
||||
|
||||
while(ss.good()) {
|
||||
std::getline(ss, view_name_var, ',');
|
||||
if (current_view()->name() == rak::trim(view_name_var)) {
|
||||
if (current_view()->name() == torrent::utils::trim_spaces_str(view_name_var)) {
|
||||
control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered.");
|
||||
return;
|
||||
}
|
||||
@@ -304,7 +300,7 @@ DownloadList::receive_exit_input(Input type) {
|
||||
if ((*current_view()->focus())->is_open())
|
||||
throw torrent::input_error("Cannot change root directory on an open download.");
|
||||
|
||||
rpc::call_command("d.directory.set", rak::trim(input->str()), rpc::make_target(*current_view()->focus()));
|
||||
rpc::call_command("d.directory.set", torrent::utils::trim_spaces_str(input->str()), rpc::make_target(*current_view()->focus()));
|
||||
control->core()->push_log_std("New root directory \"" + rpc::call_command_string("d.directory", rpc::make_target(*current_view()->focus())) + "\" for torrent.");
|
||||
break;
|
||||
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RTORRENT_UI_ELEMENT_BASE_H
|
||||
#define RTORRENT_UI_ELEMENT_BASE_H
|
||||
|
||||
|
||||
@@ -167,6 +167,8 @@ ElementDownloadList::receive_home() {
|
||||
|
||||
void
|
||||
ElementDownloadList::receive_end() {
|
||||
if (m_view->size_visible() == 0)
|
||||
return;
|
||||
m_view->set_focus(m_view->end_visible() - 1);
|
||||
m_view->set_last_changed();
|
||||
}
|
||||
@@ -221,10 +223,10 @@ ElementDownloadList::receive_change_view(const std::string& name) {
|
||||
|
||||
std::string old_name = view() ? view()->name() : "";
|
||||
if (!old_name.empty())
|
||||
rpc::commands.call_catch("event.view.hide", rpc::make_target(), name, "View hide event action failed: ");
|
||||
rpc::commands.call_catch("event.view.hide", rpc::make_target(), old_name, "View hide event action failed: ");
|
||||
set_view(*itr);
|
||||
if (!old_name.empty())
|
||||
rpc::commands.call_catch("event.view.show", rpc::make_target(), old_name, "View show event action failed: ");
|
||||
if (!name.empty())
|
||||
rpc::commands.call_catch("event.view.show", rpc::make_target(), name, "View show event action failed: ");
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1,40 +1,5 @@
|
||||
// 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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <rak/algorithm.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/data/file.h>
|
||||
#include <torrent/data/file_list.h>
|
||||
@@ -80,10 +45,10 @@ element_file_list_create_info() {
|
||||
|
||||
element->push_back("File info:");
|
||||
element->push_back("");
|
||||
|
||||
|
||||
element->push_column("Filename:", te_command("fi.filename_last="));
|
||||
element->push_back("");
|
||||
|
||||
|
||||
element->push_column("Size:", te_command("if=$fi.is_file=,$convert.xb=$f.size_bytes=,---"));
|
||||
element->push_column("Chunks:", te_command("cat=$f.completed_chunks=,\" / \",$f.size_chunks="));
|
||||
element->push_column("Range:", te_command("cat=$f.range_first=,\" - \",$f.range_second="));
|
||||
@@ -213,7 +178,7 @@ ElementFileList::receive_pagenext() {
|
||||
m_selected = iterator(fl->begin());
|
||||
|
||||
} else {
|
||||
m_selected = rak::advance_forward(m_selected, iterator(fl->end()), (m_window->height() - 1) / 2);
|
||||
m_selected = display::advance_forward(m_selected, iterator(fl->end()), (m_window->height() - 1) / 2);
|
||||
|
||||
if (m_selected == iterator(fl->end()))
|
||||
m_selected = --iterator(fl->end());
|
||||
@@ -232,7 +197,7 @@ ElementFileList::receive_pageprev() {
|
||||
if (m_selected == iterator(fl->begin()))
|
||||
m_selected = --iterator(fl->end());
|
||||
else
|
||||
m_selected = rak::advance_backward(m_selected, iterator(fl->begin()), (m_window->height() - 1) / 2);
|
||||
m_selected = display::advance_backward(m_selected, iterator(fl->begin()), (m_window->height() - 1) / 2);
|
||||
|
||||
update_itr();
|
||||
}
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RTORRENT_UI_ELEMENT_FILE_LIST_H
|
||||
#define RTORRENT_UI_ELEMENT_FILE_LIST_H
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/utils/thread.h>
|
||||
#include <torrent/system/thread.h>
|
||||
|
||||
#include "display/frame.h"
|
||||
#include "display/manager.h"
|
||||
|
||||
+5
-5
@@ -5,11 +5,11 @@
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string.h>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/throttle.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/download/resource_manager.h>
|
||||
#include <torrent/utils/log.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
|
||||
#include "control.h"
|
||||
#include "core/download_list.h"
|
||||
@@ -286,8 +286,8 @@ Root::add_to_input_history(ui::DownloadList::Input type, std::string item) {
|
||||
|
||||
// Don't store item if it's empty or the same as the last one in the category.
|
||||
if (!item.empty() && item != itr->second.at(prev_item_pointer)) {
|
||||
itr->second.at(pitr->second) = rak::trim(item);
|
||||
m_input_history_pointers[type] = (pitr->second + 1) % m_input_history_length;
|
||||
itr->second.at(pitr->second) = torrent::utils::trim_spaces_str(item);
|
||||
m_input_history_pointers[type] = (pitr->second + 1) % m_input_history_length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ Root::load_input_history() {
|
||||
InputHistory::iterator itr = input_history_tmp.find(type);
|
||||
|
||||
if (itr != input_history_tmp.end()) {
|
||||
std::string input_str = rak::trim(line.substr(delim_pos + 1));
|
||||
auto input_str = torrent::utils::trim_spaces_str(line.substr(delim_pos + 1));
|
||||
|
||||
if (!input_str.empty())
|
||||
itr->second.push_back(input_str);
|
||||
@@ -496,7 +496,7 @@ Root::set_keymap_style(const std::string& style) {
|
||||
} else if (style == "emacs") {
|
||||
m_keymap = emacs_keymap;
|
||||
} else {
|
||||
throw torrent::input_error("Root::set_keymap_style() -> ui.keymap.style is configured with unknown keymap style: " + m_keymap_style);
|
||||
throw torrent::input_error("Root::set_keymap_style() -> ui.keymap.style is configured with unknown keymap style: " + style);
|
||||
}
|
||||
|
||||
m_keymap_style = style;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ std::string
|
||||
remove_newlines(const std::string& str) {
|
||||
std::string result;
|
||||
for (auto& itr : str) {
|
||||
if (itr != '\n' || itr != '\n')
|
||||
if (itr != '\n' && itr != '\r')
|
||||
result.push_back(itr);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "utils/directory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <dirent.h>
|
||||
#include <functional>
|
||||
#include <sys/stat.h>
|
||||
@@ -19,7 +20,9 @@ Directory::is_valid() const {
|
||||
return false;
|
||||
|
||||
DIR* d = opendir(expand_path(m_path).c_str());
|
||||
closedir(d);
|
||||
|
||||
if (d != NULL)
|
||||
closedir(d);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -1,37 +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 <sundell.software@gmail.com>
|
||||
|
||||
|
||||
#ifndef RTORRENT_UTILS_DIRECTORY_H
|
||||
#define RTORRENT_UTILS_DIRECTORY_H
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "utils/gzip.h"
|
||||
|
||||
#include <zlib.h>
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
namespace utils {
|
||||
|
||||
void
|
||||
gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector<char>& output, unsigned int offset) {
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
constexpr int window_bits = 15;
|
||||
constexpr int gzip_encoding = 16;
|
||||
constexpr int gzip_level = 6;
|
||||
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits | gzip_encoding, gzip_level, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
throw torrent::internal_error("gzip_compress_to_vector(...) could not initialize gzip deflate.");
|
||||
|
||||
auto max_response_size = deflateBound(&zs, length);
|
||||
|
||||
output.resize(offset + max_response_size);
|
||||
|
||||
zs.next_in = (Bytef*)buffer;
|
||||
zs.avail_in = length;
|
||||
zs.next_out = (Bytef*)(output.data() + offset);
|
||||
zs.avail_out = max_response_size;
|
||||
|
||||
int ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
throw torrent::internal_error("gzip_compress_to_vector(...) deflate did not return Z_STREAM_END: " + std::to_string(ret));
|
||||
|
||||
output.resize(offset + max_response_size - zs.avail_out);
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef RTORRENT_UTILS_GZIP_H
|
||||
#define RTORRENT_UTILS_GZIP_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace utils {
|
||||
|
||||
void gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector<char>& output, unsigned int offset = 0);
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif
|
||||
@@ -124,12 +124,13 @@ ListFocus<Base>::dec_focus() {
|
||||
template <typename Base>
|
||||
typename ListFocus<Base>::iterator
|
||||
ListFocus<Base>::erase(iterator itr) {
|
||||
if (itr == m_focus)
|
||||
return m_focus = m_base->erase(itr);
|
||||
else
|
||||
return m_base->erase(itr);
|
||||
if (itr == m_focus) {
|
||||
m_focus = m_base->erase(itr);
|
||||
emit_changed();
|
||||
return m_focus;
|
||||
}
|
||||
|
||||
emit_changed();
|
||||
return m_base->erase(itr);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
#include "test/helpers/test_thread.h"
|
||||
#include "torrent/common.h"
|
||||
#include "torrent/utils/thread.h"
|
||||
#include "torrent/system/thread.h"
|
||||
|
||||
class TestMainThread : public torrent::utils::Thread {
|
||||
class TestMainThread : public torrent::system::Thread {
|
||||
public:
|
||||
static std::unique_ptr<TestMainThread> create();
|
||||
static std::unique_ptr<TestMainThread> create_with_mock();
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
#include "test/helpers/test_utils.h"
|
||||
#include "torrent/common.h"
|
||||
#include "torrent/utils/thread.h"
|
||||
#include "torrent/system/thread.h"
|
||||
|
||||
class test_thread : public torrent::utils::Thread {
|
||||
class test_thread : public torrent::system::Thread {
|
||||
public:
|
||||
enum test_state {
|
||||
TEST_NONE,
|
||||
|
||||
Reference in New Issue
Block a user