mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-12 21:22:31 +00:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 877e5f6ed3 | |||
| ee64f8ea50 | |||
| 224f06fdc2 | |||
| bf1265d954 | |||
| 324811d6f0 | |||
| e51fad5bd2 | |||
| 44bb2d691f | |||
| 161a36dedd | |||
| a69e44a096 | |||
| 4dd8b1fd7e | |||
| b5f6bb5652 | |||
| 3ae6bbc394 | |||
| 6c3c77a2b7 | |||
| fdfca98f1c | |||
| 31196ff5f0 | |||
| cfb9e083d2 | |||
| 5b4b607c98 | |||
| 87db1cf0f0 | |||
| 120fcb0273 | |||
| dd3ddf7c39 | |||
| e56bffff4d | |||
| d20c3234da | |||
| dda5e4fa04 | |||
| c36956813d | |||
| 9ab73352e1 | |||
| f90a96d57a | |||
| 247ae7621a | |||
| 05831942a7 | |||
| 05563b4c9b | |||
| a544f8ad68 | |||
| ee18a1f3fc | |||
| b35413ea71 | |||
| f52f20f1b6 | |||
| f9f19fb041 | |||
| 41eef969ea | |||
| 1def5e8b36 | |||
| 8355870586 | |||
| ba2bc7e64d | |||
| 0c11deac50 | |||
| f6b3ad0efd | |||
| 5d350f0bcc | |||
| 86fa0d195f | |||
| 2354c9cdb5 | |||
| 1b25bc7f56 | |||
| c6bde213b4 | |||
| 959448acda | |||
| 3b6da6feac | |||
| ea2d22cb87 |
@@ -0,0 +1,65 @@
|
|||||||
|
name: "Lint Commit Message Size"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-commit-bounds:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Validate Line Count, Width, and Spacing
|
||||||
|
run: |
|
||||||
|
# Fetch commit hashes unique to this PR branch
|
||||||
|
COMMITS=$(git log --no-merges --pretty=format:"%H" origin/${{ github.base_ref }}..HEAD)
|
||||||
|
|
||||||
|
MAX_LINES=3
|
||||||
|
MAX_CHARS=90
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
for commit in $COMMITS; do
|
||||||
|
SUBJECT=$(git log --format="%s" -n 1 $commit)
|
||||||
|
|
||||||
|
# Extract clean commit message, trimming trailing blank lines
|
||||||
|
RAW_MSG=$(git log --format="%B" -n 1 $commit)
|
||||||
|
CLEAN_MSG=$(echo "$RAW_MSG" | awk '{msg[NR]=$0} END {while(NR>0 && msg[NR]=="") NR--; for(i=1;i<=NR;i++) print msg[i]}')
|
||||||
|
|
||||||
|
# 1. Check total line count
|
||||||
|
LINE_COUNT=$(echo "$CLEAN_MSG" | wc -l)
|
||||||
|
if [ "$LINE_COUNT" -gt "$MAX_LINES" ]; then
|
||||||
|
echo "❌ Error: Commit message has too many lines ($LINE_COUNT/$MAX_LINES)."
|
||||||
|
echo " Commit: '$SUBJECT'"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Check for multiple consecutive blank lines
|
||||||
|
# This regex looks for 2 or more empty lines anywhere in the message
|
||||||
|
if echo "$CLEAN_MSG" | grep -Pz '(\r?\n){3,}'; then
|
||||||
|
echo "❌ Error: Commit message contains multiple consecutive line breaks."
|
||||||
|
echo " Commit: '$SUBJECT'"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Check maximum width of any individual line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
LINE_LENGTH=${#line}
|
||||||
|
if [ "$LINE_LENGTH" -gt "$MAX_CHARS" ]; then
|
||||||
|
echo "❌ Error: Line length exceeds limit ($LINE_LENGTH / $MAX_CHARS chars)."
|
||||||
|
echo " Offending line: '$line'"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
done <<< "$CLEAN_MSG"
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
# Fail the job if any check failed
|
||||||
|
if [ "$FAILED" -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ All commit messages passed style rules!"
|
||||||
+6
-4
@@ -1,6 +1,6 @@
|
|||||||
m4_pattern_allow([PKG_CHECK_EXISTS])
|
m4_pattern_allow([PKG_CHECK_EXISTS])
|
||||||
|
|
||||||
AC_INIT([rtorrent],[0.16.17],[sundell.software@gmail.com])
|
AC_INIT([rtorrent],[0.16.20],[sundell.software@gmail.com])
|
||||||
|
|
||||||
AC_CONFIG_HEADERS([config.h])
|
AC_CONFIG_HEADERS([config.h])
|
||||||
AC_CONFIG_MACRO_DIRS([scripts])
|
AC_CONFIG_MACRO_DIRS([scripts])
|
||||||
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
|
|||||||
|
|
||||||
PKG_PROG_PKG_CONFIG
|
PKG_PROG_PKG_CONFIG
|
||||||
|
|
||||||
AC_DEFINE([API_VERSION], [23], [api version])
|
AC_DEFINE([API_VERSION], [25], [api version])
|
||||||
|
|
||||||
RAK_CHECK_CFLAGS
|
RAK_CHECK_CFLAGS
|
||||||
RAK_CHECK_CXXFLAGS
|
RAK_CHECK_CXXFLAGS
|
||||||
@@ -22,6 +22,8 @@ RAK_ENABLE_DEBUG
|
|||||||
RAK_ENABLE_EXTRA_DEBUG
|
RAK_ENABLE_EXTRA_DEBUG
|
||||||
RAK_ENABLE_WERROR
|
RAK_ENABLE_WERROR
|
||||||
|
|
||||||
|
TORRENT_CHECK_ATOMIC
|
||||||
|
|
||||||
AC_ARG_ENABLE(execinfo,
|
AC_ARG_ENABLE(execinfo,
|
||||||
AS_HELP_STRING([--disable-execinfo],
|
AS_HELP_STRING([--disable-execinfo],
|
||||||
[disable libexecinfo [[default=enable]]]),
|
[disable libexecinfo [[default=enable]]]),
|
||||||
@@ -47,7 +49,7 @@ fi
|
|||||||
|
|
||||||
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
|
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
|
||||||
PKG_CHECK_MODULES([ZLIB], [zlib])
|
PKG_CHECK_MODULES([ZLIB], [zlib])
|
||||||
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.17])
|
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.20])
|
||||||
|
|
||||||
AC_LANG_PUSH(C++)
|
AC_LANG_PUSH(C++)
|
||||||
TORRENT_WITH_XMLRPC_C
|
TORRENT_WITH_XMLRPC_C
|
||||||
@@ -68,7 +70,7 @@ AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION)], Http user agent)
|
|||||||
|
|
||||||
dnl Only update global build variables immediately before generating the output,
|
dnl Only update global build variables immediately before generating the output,
|
||||||
dnl to avoid affecting the global build environment for other autoconf checks.
|
dnl to avoid affecting the global build environment for other autoconf checks.
|
||||||
LIBS="$PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS"
|
LIBS="$ATOMIC_LIBS $PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS"
|
||||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
|
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
|
||||||
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
|
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Resolving paths
|
||||||
|
|
||||||
|
Commands that return a path have `.realpath` variants that resolve it to a
|
||||||
|
canonical one, with symlinks followed and any `.` or `..` components removed.
|
||||||
|
|
||||||
|
The intent is to make paths safer to hand to an external script.
|
||||||
|
A script called through `execute` receives whatever path rtorrent gives it, and
|
||||||
|
many scripts do no sanity checking of their own, so resolving the path before it
|
||||||
|
leaves rtorrent removes a class of surprises: a download directory that is a
|
||||||
|
symlink into somewhere unexpected, or a torrent whose name walks upwards out of
|
||||||
|
the directory it is supposed to live in.
|
||||||
|
|
||||||
|
# Instead of this
|
||||||
|
execute = ~/bin/on-finished, (d.base_path)
|
||||||
|
|
||||||
|
# Pass the resolved path
|
||||||
|
execute = ~/bin/on-finished, (d.base_path.realpath.or_throw)
|
||||||
|
|
||||||
|
## Available variants
|
||||||
|
|
||||||
|
Each command below comes in an `.or_empty` and an `.or_throw` form.
|
||||||
|
|
||||||
|
| Command | Resolves |
|
||||||
|
| --- | --- |
|
||||||
|
| `d.base_path.realpath.*` | `d.base_path` |
|
||||||
|
| `d.directory.realpath.*` | `d.directory` |
|
||||||
|
| `d.tied_to_file.realpath.*` | `d.tied_to_file` |
|
||||||
|
| `d.loaded_file.realpath.*` | `d.loaded_file` |
|
||||||
|
| `f.frozen_path.realpath.*` | `f.frozen_path` |
|
||||||
|
| `session.path.realpath.*` | `session.path` |
|
||||||
|
| `directory.default.realpath.*` | `directory.default` |
|
||||||
|
|
||||||
|
A leading `~` is expanded first, exactly as it is for `execute`, so
|
||||||
|
`~/downloads` resolves the same way it would on the command line.
|
||||||
|
|
||||||
|
## Paths that do not exist
|
||||||
|
|
||||||
|
Resolving requires the path to name an existing file or directory, which is
|
||||||
|
often not the case for a download whose data has not been written yet.
|
||||||
|
The two forms differ only in what they do about it.
|
||||||
|
|
||||||
|
`.or_empty` returns an empty string:
|
||||||
|
|
||||||
|
print = (d.base_path.realpath.or_empty) # ""
|
||||||
|
|
||||||
|
This keeps `d.multicall` usable over a view that mixes started and unstarted
|
||||||
|
downloads, since a single unresolvable path does not abort the whole call.
|
||||||
|
A script receiving one of these paths should still check that it is not empty
|
||||||
|
before acting on it.
|
||||||
|
|
||||||
|
`.or_throw` raises an error instead:
|
||||||
|
|
||||||
|
print = (d.base_path.realpath.or_throw) # Could not resolve path: '...'
|
||||||
|
|
||||||
|
Prefer this one wherever an unresolvable path means the command should not run
|
||||||
|
at all, such as a single `execute` on `event.download.finished`.
|
||||||
|
|
||||||
|
Note that a download only has a file list once it has been opened, so
|
||||||
|
`d.base_path` and `f.frozen_path` are empty at `event.download.inserted` time
|
||||||
|
and their `.realpath` variants resolve nothing.
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# String functions
|
||||||
|
|
||||||
|
The `string.*` commands manipulate and inspect text from within the
|
||||||
|
configuration file.
|
||||||
|
They are pure functions with no side effects, so they can be nested freely and
|
||||||
|
are safe to call over RPC.
|
||||||
|
|
||||||
|
Every argument is converted to its string representation before use, which means
|
||||||
|
numbers can be passed where text is expected.
|
||||||
|
Commands that count characters, such as `string.length` and `string.substr`, count
|
||||||
|
utf-8 characters rather than bytes.
|
||||||
|
|
||||||
|
## string.length
|
||||||
|
|
||||||
|
# string.length = «text»
|
||||||
|
|
||||||
|
string.length = "héllo" # 5
|
||||||
|
|
||||||
|
Returns the number of utf-8 characters in the text.
|
||||||
|
|
||||||
|
## string.equals
|
||||||
|
|
||||||
|
# string.equals = «text», «other»[, ...]
|
||||||
|
|
||||||
|
string.equals = (d.name), "first.iso", "second.iso"
|
||||||
|
|
||||||
|
Returns `1` if the first argument equals any of the following arguments,
|
||||||
|
otherwise `0`.
|
||||||
|
|
||||||
|
## string.starts_with, string.ends_with
|
||||||
|
|
||||||
|
# string.starts_with = «text», «prefix»[, ...]
|
||||||
|
# string.ends_with = «text», «tail»[, ...]
|
||||||
|
|
||||||
|
string.starts_with = (t.url), "http://", "https://"
|
||||||
|
string.ends_with = (d.name), ".iso"
|
||||||
|
|
||||||
|
Returns `1` if the text begins, or ends, with any of the given prefixes or
|
||||||
|
tails.
|
||||||
|
|
||||||
|
## string.contains, string.contains_i
|
||||||
|
|
||||||
|
# string.contains = «haystack», «needle»[, ...]
|
||||||
|
# string.contains_i = «haystack», «needle»[, ...]
|
||||||
|
|
||||||
|
string.contains = (t.url), "retracker.local"
|
||||||
|
string.contains_i = (t.url), "RETRACKER.local"
|
||||||
|
|
||||||
|
Returns `1` if the haystack contains any of the needles.
|
||||||
|
The `_i` variant compares case-insensitively, and only handles ascii.
|
||||||
|
|
||||||
|
## string.substr
|
||||||
|
|
||||||
|
# string.substr = «text»[, «position»[, «count»[, «default»]]]
|
||||||
|
|
||||||
|
string.substr = "abcdef", 2, 3 # "cde"
|
||||||
|
string.substr = "abcdef", -2 # "ef"
|
||||||
|
string.substr = "abcdef", 10, 1, "?" # "?"
|
||||||
|
|
||||||
|
Extracts a part of the text, starting at `position` and spanning `count`
|
||||||
|
characters.
|
||||||
|
The position defaults to the start of the text, and the count to the rest of it.
|
||||||
|
A negative position is relative to the end of the text.
|
||||||
|
If the position falls outside the text, the default value is returned instead,
|
||||||
|
which is the empty string unless given.
|
||||||
|
|
||||||
|
## string.split
|
||||||
|
|
||||||
|
# string.split = «text», «delimiter»
|
||||||
|
|
||||||
|
string.split = "a.b.c", "." # {"a", "b", "c"}
|
||||||
|
string.split = "abc", "" # {"a", "b", "c"}
|
||||||
|
|
||||||
|
Splits the text into a list, keeping empty fields.
|
||||||
|
An empty delimiter splits the text into its utf-8 characters.
|
||||||
|
|
||||||
|
## string.join
|
||||||
|
|
||||||
|
# string.join = «delimiter»[, «object»[, ...]]
|
||||||
|
|
||||||
|
string.join = "-", (string.split, "a.b.c", ".") # "a-b-c"
|
||||||
|
|
||||||
|
Concatenates the objects, inserting the delimiter between them.
|
||||||
|
Lists are flattened, so the result of `string.split` can be joined back
|
||||||
|
together.
|
||||||
|
|
||||||
|
## string.lpad, string.rpad
|
||||||
|
|
||||||
|
# string.lpad = «text», «length»[, «padding»]
|
||||||
|
# string.rpad = «text», «length»[, «padding»]
|
||||||
|
|
||||||
|
string.lpad = 7, 3, 0 # "007"
|
||||||
|
string.rpad = "a", 3 # "a "
|
||||||
|
|
||||||
|
Pads the text at the start, or the end, until it is `length` characters long.
|
||||||
|
The padding defaults to a single space and is repeated as needed.
|
||||||
|
Text that is already long enough is returned unchanged.
|
||||||
|
|
||||||
|
## string.strip, string.lstrip, string.rstrip
|
||||||
|
|
||||||
|
# string.strip = «text»[, «strippable»[, ...]]
|
||||||
|
# string.lstrip = «text»[, «head»[, ...]]
|
||||||
|
# string.rstrip = «text»[, «tail»[, ...]]
|
||||||
|
|
||||||
|
string.strip = " padded " # "padded"
|
||||||
|
string.strip = "//path//", "/" # "path"
|
||||||
|
|
||||||
|
Removes characters from both ends of the text, or from only the start or the
|
||||||
|
end.
|
||||||
|
The arguments after the text form a set of utf-8 characters to remove.
|
||||||
|
Without them, whitespace is removed.
|
||||||
|
|
||||||
|
## string.map
|
||||||
|
|
||||||
|
# string.map = «text», {«old», «new»}[, ...]
|
||||||
|
|
||||||
|
string.map = (d.state), {0, "stopped"}, {1, "started"}
|
||||||
|
|
||||||
|
Returns the replacement of the first pair whose `old` value equals the whole
|
||||||
|
text.
|
||||||
|
If no pair matches, the text is returned unchanged.
|
||||||
|
|
||||||
|
## string.replace
|
||||||
|
|
||||||
|
# string.replace = «text», {«old», «new»}[, ...]
|
||||||
|
|
||||||
|
string.replace = "a-b-c", {"-", "+"} # "a+b+c"
|
||||||
|
|
||||||
|
Replaces every occurrence of `old` with `new`.
|
||||||
|
The pairs are applied in order, so a later pair operates on the result of the
|
||||||
|
earlier ones.
|
||||||
|
|
||||||
|
## Example: dropping unwanted trackers
|
||||||
|
|
||||||
|
The following disables every tracker that points at `retracker.local` as soon as
|
||||||
|
a download is inserted.
|
||||||
|
|
||||||
|
method.set_key = event.download.inserted, drop_retracker, \
|
||||||
|
((t.multicall, default, "branch=(string.contains,(t.url),retracker.local),((t.disable))"))
|
||||||
+2
-2
@@ -71,11 +71,11 @@
|
|||||||
|
|
||||||
# Port range to use for listening.
|
# Port range to use for listening.
|
||||||
#
|
#
|
||||||
#network.port_range.set = 6890-6999
|
#network.listen.port.range.set = 6890-6999
|
||||||
|
|
||||||
# Start opening ports at a random position within the port range.
|
# Start opening ports at a random position within the port range.
|
||||||
#
|
#
|
||||||
#network.port_random.set = no
|
#network.listen.port.random.set = no
|
||||||
|
|
||||||
# Set RPC type
|
# Set RPC type
|
||||||
#network.rpc.use_xmlrpc.set = true
|
#network.rpc.use_xmlrpc.set = true
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ execute.throw = sh, -c, (cat,\
|
|||||||
"\"",(cfg.watch),"/start\" ")
|
"\"",(cfg.watch),"/start\" ")
|
||||||
|
|
||||||
# Listening port for incoming peer traffic (fixed; you can also randomize it)
|
# Listening port for incoming peer traffic (fixed; you can also randomize it)
|
||||||
network.port_range.set = 50000-50000
|
network.listen.port.range.set = 50000-50000
|
||||||
network.port_random.set = no
|
network.listen.port.random.set = no
|
||||||
|
|
||||||
# Tracker-less torrent and UDP tracker support
|
# Tracker-less torrent and UDP tracker support
|
||||||
# (conservative settings for 'private' trackers, change for 'public')
|
# (conservative settings for 'private' trackers, change for 'public')
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ libsub_root_a_SOURCES = \
|
|||||||
utils/list_focus.h \
|
utils/list_focus.h \
|
||||||
utils/lockfile.cc \
|
utils/lockfile.cc \
|
||||||
utils/lockfile.h \
|
utils/lockfile.h \
|
||||||
|
utils/waitpid_queue.cc \
|
||||||
|
utils/waitpid_queue.h \
|
||||||
utils/watch_ready_queue.cc \
|
utils/watch_ready_queue.cc \
|
||||||
utils/watch_ready_queue.h \
|
utils/watch_ready_queue.h \
|
||||||
\
|
\
|
||||||
@@ -192,6 +194,7 @@ libsub_root_a_SOURCES = \
|
|||||||
command_throttle.cc \
|
command_throttle.cc \
|
||||||
command_tracker.cc \
|
command_tracker.cc \
|
||||||
command_scheduler.cc \
|
command_scheduler.cc \
|
||||||
|
command_string.cc \
|
||||||
command_ui.cc \
|
command_ui.cc \
|
||||||
control.cc \
|
control.cc \
|
||||||
control.h \
|
control.h \
|
||||||
|
|||||||
@@ -655,6 +655,8 @@ initialize_command_download() {
|
|||||||
CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); });
|
CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); });
|
||||||
CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); });
|
CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); });
|
||||||
CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); });
|
CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); });
|
||||||
|
CMD2_DL("d.base_path.realpath.or_empty", [](auto* download, auto) { return resolve_path(retrieve_d_base_path(download).str()); });
|
||||||
|
CMD2_DL("d.base_path.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(retrieve_d_base_path(download).str()); });
|
||||||
CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); });
|
CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); });
|
||||||
CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); });
|
CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); });
|
||||||
CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
|
CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
|
||||||
@@ -771,6 +773,11 @@ initialize_command_download() {
|
|||||||
CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file");
|
CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file");
|
||||||
CMD2_DL_VAR_STRING("d.loaded_file", "rtorrent", "loaded_file");
|
CMD2_DL_VAR_STRING("d.loaded_file", "rtorrent", "loaded_file");
|
||||||
|
|
||||||
|
CMD2_DL("d.tied_to_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
|
||||||
|
CMD2_DL("d.tied_to_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
|
||||||
|
CMD2_DL("d.loaded_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
|
||||||
|
CMD2_DL("d.loaded_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
|
||||||
|
|
||||||
// The "state_changed" variable is required to be a valid unix time
|
// The "state_changed" variable is required to be a valid unix time
|
||||||
// value, it indicates the last time the torrent changed its state,
|
// value, it indicates the last time the torrent changed its state,
|
||||||
// resume/pause.
|
// resume/pause.
|
||||||
@@ -878,6 +885,8 @@ initialize_command_download() {
|
|||||||
CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); });
|
CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); });
|
||||||
|
|
||||||
CMD2_DL ("d.directory", CMD2_ON_FL(root_dir));
|
CMD2_DL ("d.directory", CMD2_ON_FL(root_dir));
|
||||||
|
CMD2_DL ("d.directory.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); });
|
||||||
|
CMD2_DL ("d.directory.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(download->file_list()->root_dir()); });
|
||||||
CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2));
|
CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2));
|
||||||
CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir));
|
CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir));
|
||||||
CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2));
|
CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2));
|
||||||
@@ -912,6 +921,14 @@ initialize_command_download() {
|
|||||||
rpc::rpc.mark_safe("d.local_id_html");
|
rpc::rpc.mark_safe("d.local_id_html");
|
||||||
rpc::rpc.mark_safe("d.bitfield");
|
rpc::rpc.mark_safe("d.bitfield");
|
||||||
rpc::rpc.mark_safe("d.base_path");
|
rpc::rpc.mark_safe("d.base_path");
|
||||||
|
rpc::rpc.mark_safe("d.base_path.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("d.base_path.realpath.or_throw");
|
||||||
|
rpc::rpc.mark_safe("d.directory.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("d.directory.realpath.or_throw");
|
||||||
|
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_throw");
|
||||||
|
rpc::rpc.mark_safe("d.loaded_file.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("d.loaded_file.realpath.or_throw");
|
||||||
rpc::rpc.mark_safe("d.base_path.hex");
|
rpc::rpc.mark_safe("d.base_path.hex");
|
||||||
rpc::rpc.mark_safe("d.base_path.base64");
|
rpc::rpc.mark_safe("d.base_path.base64");
|
||||||
rpc::rpc.mark_safe("d.base_path.base64_as_binary");
|
rpc::rpc.mark_safe("d.base_path.base64_as_binary");
|
||||||
@@ -974,6 +991,7 @@ initialize_command_download() {
|
|||||||
rpc::rpc.mark_safe("d.size_pex");
|
rpc::rpc.mark_safe("d.size_pex");
|
||||||
rpc::rpc.mark_safe("d.completed_bytes");
|
rpc::rpc.mark_safe("d.completed_bytes");
|
||||||
rpc::rpc.mark_safe("d.complete");
|
rpc::rpc.mark_safe("d.complete");
|
||||||
|
rpc::rpc.mark_safe("d.timestamp.started");
|
||||||
rpc::rpc.mark_safe("d.timestamp.finished");
|
rpc::rpc.mark_safe("d.timestamp.finished");
|
||||||
rpc::rpc.mark_safe("d.bytes_done");
|
rpc::rpc.mark_safe("d.bytes_done");
|
||||||
rpc::rpc.mark_safe("d.peers_accounted");
|
rpc::rpc.mark_safe("d.peers_accounted");
|
||||||
|
|||||||
+16
-10
@@ -444,16 +444,20 @@ initialize_command_dynamic() {
|
|||||||
|
|
||||||
CMD2_ANY ("catch", std::bind(&cmd_catch, std::placeholders::_1, std::placeholders::_2));
|
CMD2_ANY ("catch", std::bind(&cmd_catch, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
|
||||||
CMD2_ANY ("strings.choke_heuristics", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS));
|
CMD2_ANY_STRING ("enum.log_group", [](auto, const auto& str) { return torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, str); });
|
||||||
CMD2_ANY ("strings.choke_heuristics.upload", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS_UPLOAD));
|
|
||||||
CMD2_ANY ("strings.choke_heuristics.download", std::bind(&torrent::option_list_strings, torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD));
|
CMD2_ANY ("strings.choke_heuristics", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS); });
|
||||||
CMD2_ANY ("strings.connection_type", std::bind(&torrent::option_list_strings, torrent::OPTION_CONNECTION_TYPE));
|
CMD2_ANY ("strings.choke_heuristics.upload", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_UPLOAD); });
|
||||||
CMD2_ANY ("strings.encryption", std::bind(&torrent::option_list_strings, torrent::OPTION_ENCRYPTION));
|
CMD2_ANY ("strings.choke_heuristics.download", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD); });
|
||||||
CMD2_ANY ("strings.ip_filter", std::bind(&torrent::option_list_strings, torrent::OPTION_IP_FILTER));
|
CMD2_ANY ("strings.connection_type", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_CONNECTION_TYPE); });
|
||||||
CMD2_ANY ("strings.ip_tos", std::bind(&torrent::option_list_strings, torrent::OPTION_IP_TOS));
|
CMD2_ANY ("strings.encryption", [](auto, auto) { return torrent::Object::create_list(); });
|
||||||
CMD2_ANY ("strings.log_group", std::bind(&torrent::option_list_strings, torrent::OPTION_LOG_GROUP));
|
CMD2_ANY ("strings.encryption.handshake", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_ENCRYPTION_HANDSHAKE); });
|
||||||
CMD2_ANY ("strings.tracker_event", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_EVENT));
|
CMD2_ANY ("strings.encryption.stream", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_ENCRYPTION_STREAM); });
|
||||||
CMD2_ANY ("strings.tracker_mode", std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_MODE));
|
CMD2_ANY ("strings.ip_filter", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_IP_FILTER); });
|
||||||
|
CMD2_ANY ("strings.ip_tos", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_IP_TOS); });
|
||||||
|
CMD2_ANY ("strings.log_group", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_LOG_GROUP); });
|
||||||
|
CMD2_ANY ("strings.tracker_event", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_TRACKER_EVENT); });
|
||||||
|
CMD2_ANY ("strings.tracker_mode", [](auto, auto) { return torrent::option_list_strings(torrent::OPTION_TRACKER_MODE); });
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#ifdef HAVE_XMLRPC_TINYXML2
|
#ifdef HAVE_XMLRPC_TINYXML2
|
||||||
@@ -468,6 +472,8 @@ initialize_command_dynamic() {
|
|||||||
rpc::rpc.mark_safe("method.rlookup");
|
rpc::rpc.mark_safe("method.rlookup");
|
||||||
rpc::rpc.mark_safe("catch");
|
rpc::rpc.mark_safe("catch");
|
||||||
|
|
||||||
|
rpc::rpc.mark_safe("enum.log_group");
|
||||||
|
|
||||||
rpc::rpc.mark_safe("strings.choke_heuristics");
|
rpc::rpc.mark_safe("strings.choke_heuristics");
|
||||||
rpc::rpc.mark_safe("strings.choke_heuristics.upload");
|
rpc::rpc.mark_safe("strings.choke_heuristics.upload");
|
||||||
rpc::rpc.mark_safe("strings.choke_heuristics.download");
|
rpc::rpc.mark_safe("strings.choke_heuristics.download");
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ initialize_command_file() {
|
|||||||
CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); });
|
CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); });
|
||||||
CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); });
|
CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); });
|
||||||
CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); });
|
CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); });
|
||||||
|
CMD2_FILE("f.frozen_path.realpath.or_empty", [](auto* file, auto) { return resolve_path(file->frozen_path().str()); });
|
||||||
|
CMD2_FILE("f.frozen_path.realpath.or_throw", [](auto* file, auto) { return resolve_path_or_throw(file->frozen_path().str()); });
|
||||||
|
|
||||||
CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1));
|
CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1));
|
||||||
CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1));
|
CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1));
|
||||||
@@ -129,6 +131,8 @@ initialize_command_file() {
|
|||||||
rpc::rpc.mark_safe("f.path_components");
|
rpc::rpc.mark_safe("f.path_components");
|
||||||
rpc::rpc.mark_safe("f.path_depth");
|
rpc::rpc.mark_safe("f.path_depth");
|
||||||
rpc::rpc.mark_safe("f.frozen_path");
|
rpc::rpc.mark_safe("f.frozen_path");
|
||||||
|
rpc::rpc.mark_safe("f.frozen_path.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("f.frozen_path.realpath.or_throw");
|
||||||
rpc::rpc.mark_safe("f.frozen_path.hex");
|
rpc::rpc.mark_safe("f.frozen_path.hex");
|
||||||
rpc::rpc.mark_safe("f.frozen_path.base64");
|
rpc::rpc.mark_safe("f.frozen_path.base64");
|
||||||
rpc::rpc.mark_safe("f.frozen_path.base64_as_binary");
|
rpc::rpc.mark_safe("f.frozen_path.base64_as_binary");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ void initialize_command_groups();
|
|||||||
void initialize_command_throttle();
|
void initialize_command_throttle();
|
||||||
void initialize_command_tracker();
|
void initialize_command_tracker();
|
||||||
void initialize_command_scheduler();
|
void initialize_command_scheduler();
|
||||||
|
void initialize_command_string();
|
||||||
void initialize_command_ui();
|
void initialize_command_ui();
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -37,4 +38,5 @@ initialize_commands() {
|
|||||||
initialize_command_throttle();
|
initialize_command_throttle();
|
||||||
initialize_command_tracker();
|
initialize_command_tracker();
|
||||||
initialize_command_scheduler();
|
initialize_command_scheduler();
|
||||||
|
initialize_command_string();
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-32
@@ -8,10 +8,10 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <torrent/torrent.h>
|
#include <torrent/torrent.h>
|
||||||
#include <torrent/chunk_manager.h>
|
|
||||||
#include <torrent/data/file_manager.h>
|
#include <torrent/data/file_manager.h>
|
||||||
#include <torrent/data/chunk_utils.h>
|
#include <torrent/data/chunk_utils.h>
|
||||||
#include <torrent/runtime/runtime.h>
|
#include <torrent/runtime/runtime.h>
|
||||||
|
#include <torrent/runtime/memory_manager.h>
|
||||||
#include <torrent/runtime/socket_manager.h>
|
#include <torrent/runtime/socket_manager.h>
|
||||||
#include <torrent/utils/chrono.h>
|
#include <torrent/utils/chrono.h>
|
||||||
#include <torrent/utils/option_strings.h>
|
#include <torrent/utils/option_strings.h>
|
||||||
@@ -117,7 +117,7 @@ group_insert(const torrent::Object::list_type& args) {
|
|||||||
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.enable", "simple",
|
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.enable", "simple",
|
||||||
"schedule=group." + name + ".ratio,5,60,on_ratio=" + name));
|
"schedule=group." + name + ".ratio,5,60,on_ratio=" + name));
|
||||||
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.disable", "simple",
|
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.disable", "simple",
|
||||||
"schedule_remove=group." + name + ".ratio"));
|
"schedule.remove=group." + name + ".ratio"));
|
||||||
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.command", "simple",
|
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".ratio.command", "simple",
|
||||||
"d.try_close= ;d.ignore_commands.set=1"));
|
"d.try_close= ;d.ignore_commands.set=1"));
|
||||||
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".view", "string", view));
|
rpc::commands.call("method.insert", rpc::create_object_list("group." + name + ".view", "string", view));
|
||||||
@@ -188,13 +188,8 @@ cmd_file_append(const torrent::Object::list_type& args) {
|
|||||||
void
|
void
|
||||||
initialize_command_local() {
|
initialize_command_local() {
|
||||||
core::DownloadList* dList = control->core()->download_list();
|
core::DownloadList* dList = control->core()->download_list();
|
||||||
torrent::ChunkManager* chunkManager = torrent::chunk_manager();
|
|
||||||
torrent::FileManager* fileManager = torrent::file_manager();
|
torrent::FileManager* fileManager = torrent::file_manager();
|
||||||
|
|
||||||
if (rpc::call_command_value("method.use_deprecated") == 1) {
|
|
||||||
CMD_ANY_LIST ("file.append", std::bind(&cmd_file_append, std::placeholders::_2));
|
|
||||||
}
|
|
||||||
|
|
||||||
CMD_ANY ("system.hostname", std::bind(&system_hostname));
|
CMD_ANY ("system.hostname", std::bind(&system_hostname));
|
||||||
CMD_ANY ("system.pid", std::bind(&getpid));
|
CMD_ANY ("system.pid", std::bind(&getpid));
|
||||||
|
|
||||||
@@ -248,6 +243,8 @@ initialize_command_local() {
|
|||||||
CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); });
|
CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); });
|
||||||
CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { return torrent::runtime::socket_manager()->set_max_size_and_adjust(value); });
|
CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { return torrent::runtime::socket_manager()->set_max_size_and_adjust(value); });
|
||||||
CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); });
|
CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); });
|
||||||
|
CMD_ANY ("system.sockets.reserved_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->reserved_allocation(); });
|
||||||
|
CMD_ANY ("system.sockets.available_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->available_allocation(); });
|
||||||
|
|
||||||
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
|
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
|
||||||
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
|
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
|
||||||
@@ -255,49 +252,59 @@ initialize_command_local() {
|
|||||||
|
|
||||||
CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); });
|
CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); });
|
||||||
CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); });
|
CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); });
|
||||||
CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); });
|
|
||||||
CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); });
|
|
||||||
|
|
||||||
if (i == 0)
|
if (i == 0) {
|
||||||
|
CMD_ANY (category_name + ".min_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->generic_min_allocation(); });
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMD_ANY (category_name + ".max_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_limit(category); });
|
||||||
|
CMD_ANY (category_name + ".min_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_minimum(category); });
|
||||||
|
CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); });
|
||||||
|
CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); });
|
||||||
CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); });
|
CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); });
|
||||||
CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_max_allocation(category, value); });
|
CMD_ANY_VALUE_V(category_name + ".max_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_max_allocation(category, value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
CMD_ANY ("pieces.sync.always_safe", std::bind(&CM_t::safe_sync, chunkManager));
|
CMD_ANY ("pieces.sync.always_safe", [](auto, auto) { return torrent::runtime::memory_manager()->safe_sync(); });
|
||||||
CMD_ANY_VALUE_V ("pieces.sync.always_safe.set", std::bind(&CM_t::set_safe_sync, chunkManager, std::placeholders::_2));
|
CMD_ANY_VALUE_V ("pieces.sync.always_safe.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_safe_sync(value); });
|
||||||
CMD_ANY ("pieces.sync.safe_free_diskspace", std::bind(&CM_t::safe_free_diskspace, chunkManager));
|
CMD_ANY ("pieces.sync.safe_free_diskspace", [](auto, auto) { return torrent::runtime::memory_manager()->sync_safe_free_diskspace(); });
|
||||||
CMD_ANY ("pieces.sync.timeout", std::bind(&CM_t::timeout_sync, chunkManager));
|
CMD_ANY ("pieces.sync.timeout", [](auto, auto) { return torrent::runtime::memory_manager()->timeout_sync().count(); });
|
||||||
CMD_ANY_VALUE_V ("pieces.sync.timeout.set", std::bind(&CM_t::set_timeout_sync, chunkManager, std::placeholders::_2));
|
CMD_ANY_VALUE_V ("pieces.sync.timeout.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_timeout_sync(value); });
|
||||||
CMD_ANY ("pieces.sync.timeout_safe", std::bind(&CM_t::timeout_safe_sync, chunkManager));
|
// CMD_ANY ("pieces.sync.timeout_safe", [](auto, auto) { return torrent::runtime::memory_manager()->timeout_safe_sync(); });
|
||||||
CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", std::bind(&CM_t::set_timeout_safe_sync, chunkManager, std::placeholders::_2));
|
// CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_timeout_safe_sync(value); });
|
||||||
CMD_ANY ("pieces.sync.queue_size", std::bind(&CM_t::sync_queue_size, chunkManager));
|
CMD_ANY ("pieces.sync.timeout_safe", [](auto, auto) { return 0; });
|
||||||
|
CMD_ANY_VALUE_V ("pieces.sync.timeout_safe.set", [](auto, auto) { });
|
||||||
|
CMD_ANY ("pieces.sync.queue_size", [](auto, auto) { return torrent::runtime::memory_manager()->sync_queue_block_count(); });
|
||||||
|
|
||||||
CMD_ANY ("pieces.preload.type", std::bind(&CM_t::preload_type, chunkManager));
|
CMD_ANY ("pieces.preload.type", [](auto, auto) { return torrent::runtime::memory_manager()->preload_type(); });
|
||||||
CMD_ANY_VALUE_V ("pieces.preload.type.set", std::bind(&CM_t::set_preload_type, chunkManager, std::placeholders::_2));
|
CMD_ANY_VALUE_V ("pieces.preload.type.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_preload_type(value); });
|
||||||
CMD_ANY ("pieces.preload.min_size", std::bind(&CM_t::preload_min_size, chunkManager));
|
CMD_ANY ("pieces.preload.min_size", [](auto, auto) { return torrent::runtime::memory_manager()->preload_min_size(); });
|
||||||
CMD_ANY_VALUE_V ("pieces.preload.min_size.set", std::bind(&CM_t::set_preload_min_size, chunkManager, std::placeholders::_2));
|
CMD_ANY_VALUE_V ("pieces.preload.min_size.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_preload_min_size(value); });
|
||||||
CMD_ANY ("pieces.preload.min_rate", std::bind(&CM_t::preload_required_rate, chunkManager));
|
CMD_ANY ("pieces.preload.min_rate", [](auto, auto) { return torrent::runtime::memory_manager()->preload_required_rate(); });
|
||||||
CMD_ANY_VALUE_V ("pieces.preload.min_rate.set", std::bind(&CM_t::set_preload_required_rate, chunkManager, std::placeholders::_2));
|
CMD_ANY_VALUE_V ("pieces.preload.min_rate.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_preload_required_rate(value); });
|
||||||
|
|
||||||
CMD_ANY ("pieces.memory.current", std::bind(&CM_t::memory_usage, chunkManager));
|
|
||||||
CMD_ANY ("pieces.memory.sync_queue", std::bind(&CM_t::sync_queue_memory_usage, chunkManager));
|
|
||||||
CMD_ANY ("pieces.memory.block_count", std::bind(&CM_t::memory_block_count, chunkManager));
|
|
||||||
CMD_ANY ("pieces.memory.max", std::bind(&CM_t::max_memory_usage, chunkManager));
|
|
||||||
CMD_ANY_VALUE_V ("pieces.memory.max.set", std::bind(&CM_t::set_max_memory_usage, chunkManager, std::placeholders::_2));
|
|
||||||
CMD_ANY ("pieces.stats_preloaded", std::bind(&CM_t::stats_preloaded, chunkManager));
|
|
||||||
CMD_ANY ("pieces.stats_not_preloaded", std::bind(&CM_t::stats_not_preloaded, chunkManager));
|
|
||||||
|
|
||||||
|
CMD_ANY ("pieces.stats_preloaded", [](auto, auto) { return torrent::runtime::memory_manager()->stats_preloaded(); });
|
||||||
|
CMD_ANY ("pieces.stats_not_preloaded", [](auto, auto) { return torrent::runtime::memory_manager()->stats_not_preloaded(); });
|
||||||
CMD_ANY ("pieces.stats.total_size", std::bind(&apply_pieces_stats_total_size));
|
CMD_ANY ("pieces.stats.total_size", std::bind(&apply_pieces_stats_total_size));
|
||||||
|
|
||||||
|
CMD_ANY ("pieces.memory.current", [](auto, auto) { return torrent::runtime::memory_manager()->memory_usage(); });
|
||||||
|
CMD_ANY ("pieces.memory.sync_queue", [](auto, auto) { return torrent::runtime::memory_manager()->sync_queue_memory_usage(); });
|
||||||
|
CMD_ANY ("pieces.memory.block_count", [](auto, auto) { return torrent::runtime::memory_manager()->memory_block_count(); });
|
||||||
|
CMD_ANY ("pieces.memory.max", [](auto, auto) { return torrent::runtime::memory_manager()->max_memory_usage(); });
|
||||||
|
CMD_ANY_VALUE_V ("pieces.memory.max.set", [](auto, auto& value) { return torrent::runtime::memory_manager()->set_max_memory_usage(value); });
|
||||||
|
|
||||||
CMD_ANY ("pieces.hash.queue_size", std::bind(&torrent::main_thread::hash_queue_size));
|
CMD_ANY ("pieces.hash.queue_size", std::bind(&torrent::main_thread::hash_queue_size));
|
||||||
CMD_VAR_BOOL ("pieces.hash.on_completion", true);
|
CMD_VAR_BOOL ("pieces.hash.on_completion", true);
|
||||||
|
|
||||||
CMD_VAR_STRING ("directory.default", "./");
|
CMD_VAR_STRING ("directory.default", "./");
|
||||||
|
CMD_ANY ("directory.default.realpath.or_empty", [](auto, auto) { return resolve_path(rpc::call_command_string("directory.default")); });
|
||||||
|
CMD_ANY ("directory.default.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(rpc::call_command_string("directory.default")); });
|
||||||
|
|
||||||
CMD_VAR_STRING ("session.name", "");
|
CMD_VAR_STRING ("session.name", "");
|
||||||
CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); });
|
CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); });
|
||||||
|
CMD_ANY ("session.path.realpath.or_empty", [](auto, auto) { return resolve_path(session_thread::manager()->path()); });
|
||||||
|
CMD_ANY ("session.path.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(session_thread::manager()->path()); });
|
||||||
CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); });
|
CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); });
|
||||||
CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); });
|
CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); });
|
||||||
CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); });
|
CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); });
|
||||||
@@ -351,18 +358,32 @@ initialize_command_local() {
|
|||||||
|
|
||||||
rpc::rpc.mark_safe("system.sockets.size");
|
rpc::rpc.mark_safe("system.sockets.size");
|
||||||
rpc::rpc.mark_safe("system.sockets.max_size");
|
rpc::rpc.mark_safe("system.sockets.max_size");
|
||||||
|
rpc::rpc.mark_safe("system.sockets.reserved_alloc");
|
||||||
|
rpc::rpc.mark_safe("system.sockets.available_alloc");
|
||||||
|
|
||||||
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
|
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
|
||||||
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
|
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
|
||||||
|
|
||||||
rpc::rpc.mark_safe(category_name + ".size");
|
rpc::rpc.mark_safe(category_name + ".size");
|
||||||
rpc::rpc.mark_safe(category_name + ".max_size");
|
rpc::rpc.mark_safe(category_name + ".max_size");
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
rpc::rpc.mark_safe(category_name + ".min_alloc");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc::rpc.mark_safe(category_name + ".max_alloc.limit");
|
||||||
|
rpc::rpc.mark_safe(category_name + ".min_alloc.limit");
|
||||||
rpc::rpc.mark_safe(category_name + ".min_alloc");
|
rpc::rpc.mark_safe(category_name + ".min_alloc");
|
||||||
rpc::rpc.mark_safe(category_name + ".max_alloc");
|
rpc::rpc.mark_safe(category_name + ".max_alloc");
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc::rpc.mark_safe("directory.default");
|
rpc::rpc.mark_safe("directory.default");
|
||||||
rpc::rpc.mark_safe("session.path");
|
rpc::rpc.mark_safe("session.path");
|
||||||
|
rpc::rpc.mark_safe("session.path.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("session.path.realpath.or_throw");
|
||||||
|
rpc::rpc.mark_safe("directory.default.realpath.or_empty");
|
||||||
|
rpc::rpc.mark_safe("directory.default.realpath.or_throw");
|
||||||
rpc::rpc.mark_safe("session.use_lock");
|
rpc::rpc.mark_safe("session.use_lock");
|
||||||
rpc::rpc.mark_safe("session.on_completion");
|
rpc::rpc.mark_safe("session.on_completion");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <iterator>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <torrent/data/chunk_utils.h>
|
#include <torrent/data/chunk_utils.h>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
#include "core/download.h"
|
#include "core/download.h"
|
||||||
#include "core/download_list.h"
|
#include "core/download_list.h"
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
|
#include "rpc/parse.h"
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
|
|
||||||
torrent::Object
|
torrent::Object
|
||||||
@@ -38,6 +40,32 @@ apply_log_add_output(const torrent::Object::list_type& args) {
|
|||||||
return torrent::Object();
|
return torrent::Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_log_print(const torrent::Object::list_type& args) {
|
||||||
|
if (args.size() < 2)
|
||||||
|
throw torrent::input_error("Invalid number of arguments.");
|
||||||
|
|
||||||
|
torrent::Object::value_type group;
|
||||||
|
|
||||||
|
if (args.front().is_value())
|
||||||
|
group = args.front().as_value();
|
||||||
|
else if (args.front().is_string())
|
||||||
|
group = torrent::option_find_string_str(torrent::OPTION_LOG_GROUP, args.front().as_string());
|
||||||
|
else
|
||||||
|
throw torrent::input_error("Invalid log group.");
|
||||||
|
|
||||||
|
if (group < 0 || group >= torrent::LOG_GROUP_MAX_SIZE)
|
||||||
|
throw torrent::input_error("Invalid log group.");
|
||||||
|
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
for (auto itr = std::next(args.begin()); itr != args.end(); ++itr)
|
||||||
|
rpc::print_object_std(&message, &*itr, 0);
|
||||||
|
|
||||||
|
torrent::log_groups[group].internal_print(message);
|
||||||
|
return torrent::Object();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Deprecated.
|
// TODO: Deprecated.
|
||||||
torrent::Object
|
torrent::Object
|
||||||
apply_log(const torrent::Object::string_type& arg, int logType) {
|
apply_log(const torrent::Object::string_type& arg, int logType) {
|
||||||
@@ -112,6 +140,7 @@ initialize_command_logging() {
|
|||||||
CMD2_ANY_STRING_V("log.close", std::bind(&torrent::log_close_output_str, std::placeholders::_2));
|
CMD2_ANY_STRING_V("log.close", std::bind(&torrent::log_close_output_str, std::placeholders::_2));
|
||||||
|
|
||||||
CMD2_ANY_LIST ("log.add_output", std::bind(&apply_log_add_output, std::placeholders::_2));
|
CMD2_ANY_LIST ("log.add_output", std::bind(&apply_log_add_output, std::placeholders::_2));
|
||||||
|
CMD2_ANY_LIST ("log.print", std::bind(&apply_log_print, std::placeholders::_2));
|
||||||
|
|
||||||
CMD2_ANY_STRING ("log.execute", std::bind(&apply_log, std::placeholders::_2, 0));
|
CMD2_ANY_STRING ("log.execute", std::bind(&apply_log, std::placeholders::_2, 0));
|
||||||
CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2));
|
CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2));
|
||||||
|
|||||||
+124
-21
@@ -9,7 +9,9 @@
|
|||||||
#include <torrent/download/resource_manager.h>
|
#include <torrent/download/resource_manager.h>
|
||||||
#include <torrent/net/http_stack.h>
|
#include <torrent/net/http_stack.h>
|
||||||
#include <torrent/net/socket_address.h>
|
#include <torrent/net/socket_address.h>
|
||||||
|
#include <torrent/runtime/client_config.h>
|
||||||
#include <torrent/runtime/network_config.h>
|
#include <torrent/runtime/network_config.h>
|
||||||
|
#include <torrent/runtime/network_manager.h>
|
||||||
#include <torrent/runtime/proxy_manager.h>
|
#include <torrent/runtime/proxy_manager.h>
|
||||||
#include <torrent/runtime/runtime.h>
|
#include <torrent/runtime/runtime.h>
|
||||||
#include <torrent/runtime/socket_manager.h>
|
#include <torrent/runtime/socket_manager.h>
|
||||||
@@ -33,21 +35,115 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
torrent::Object
|
torrent::Object
|
||||||
apply_encryption(const torrent::Object::list_type& args) {
|
listen_port_range() {
|
||||||
uint32_t options_mask = torrent::runtime::NetworkConfig::encryption_none;
|
auto port_range = torrent::runtime::client_config()->listen_port_range();
|
||||||
|
|
||||||
for (const auto& arg : args) {
|
return std::to_string(port_range.first) + "-" + std::to_string(port_range.second);
|
||||||
uint32_t opt = torrent::option_find_string(torrent::OPTION_ENCRYPTION, arg.as_string().c_str());
|
}
|
||||||
|
|
||||||
if (opt == torrent::runtime::NetworkConfig::encryption_none)
|
void
|
||||||
options_mask = torrent::runtime::NetworkConfig::encryption_none;
|
set_listen_port_range(const std::string& arg) {
|
||||||
else
|
unsigned int port_first{}, port_last{};
|
||||||
options_mask |= opt;
|
|
||||||
|
if (std::sscanf(arg.c_str(), "%i-%i", &port_first, &port_last) != 2)
|
||||||
|
throw torrent::input_error("Invalid port_range argument.");
|
||||||
|
|
||||||
|
if (port_first >= (1 << 16) || port_last >= (1 << 16))
|
||||||
|
throw torrent::input_error("Port range out-of-bounds.");
|
||||||
|
|
||||||
|
torrent::runtime::client_config()->set_listen_port_range(port_first, port_last);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
get_encryption() {
|
||||||
|
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
|
||||||
|
|
||||||
|
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_HANDSHAKE, encryption_modes.first) + "," +
|
||||||
|
torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_STREAM, encryption_modes.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
get_handshake_encryption() {
|
||||||
|
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
|
||||||
|
|
||||||
|
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE, encryption_modes.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
get_stream_encryption() {
|
||||||
|
auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
|
||||||
|
|
||||||
|
return torrent::option_to_str_or_throw(torrent::OPTION_ENCRYPTION_MODE, encryption_modes.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_obsolete_encryption(const torrent::Object::list_type& args) {
|
||||||
|
torrent::encryption_mode handshake_mode{torrent::ENCRYPTION_MODE_ALLOW};
|
||||||
|
torrent::encryption_mode stream_mode{torrent::ENCRYPTION_MODE_ALLOW};
|
||||||
|
|
||||||
|
for (auto& itr : args) {
|
||||||
|
auto arg = itr.as_string();
|
||||||
|
|
||||||
|
if (arg == "none") {
|
||||||
|
handshake_mode = torrent::ENCRYPTION_MODE_DENY;
|
||||||
|
stream_mode = torrent::ENCRYPTION_MODE_DENY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else if (arg == "allow_incoming") {
|
||||||
|
} else if (arg == "try_outgoing") {
|
||||||
|
} else if (arg == "require") {
|
||||||
|
handshake_mode = torrent::ENCRYPTION_MODE_REQUIRE;
|
||||||
|
|
||||||
|
} else if (arg == "require_RC4" || arg == "require_rc4") {
|
||||||
|
handshake_mode = torrent::ENCRYPTION_MODE_REQUIRE;
|
||||||
|
stream_mode = torrent::ENCRYPTION_MODE_REQUIRE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else if (arg == "enable_retry") {
|
||||||
|
} else if (arg == "prefer_plaintext") {
|
||||||
|
} else {
|
||||||
|
throw torrent::input_error("Invalid encryption option: '" + arg + "'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::runtime::network_config()->set_encryption_options(options_mask);
|
lt_log_print(torrent::LOG_WARN, "Obsolete encryption options used, use 'handshake_{deny,allow,prefer,require}, stream_{deny,allow,prefer,require}' instead.");
|
||||||
|
|
||||||
return torrent::Object();
|
torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_encryption(const torrent::Object::list_type& args) {
|
||||||
|
if (args.empty())
|
||||||
|
throw torrent::input_error("No encryption options specified.");
|
||||||
|
|
||||||
|
torrent::encryption_mode encryption_mode, handshake_mode, stream_mode;
|
||||||
|
|
||||||
|
if (args.size() == 1) {
|
||||||
|
try {
|
||||||
|
encryption_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_MODE, args.front().as_string()));
|
||||||
|
|
||||||
|
} catch (torrent::input_error& e) {
|
||||||
|
return apply_obsolete_encryption(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::runtime::network_config()->set_encryption_modes(encryption_mode, encryption_mode);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() != 2)
|
||||||
|
return apply_obsolete_encryption(args);
|
||||||
|
|
||||||
|
try {
|
||||||
|
handshake_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_HANDSHAKE, args.front().as_string()));
|
||||||
|
stream_mode = static_cast<torrent::encryption_mode>(torrent::option_find_string_str(torrent::OPTION_ENCRYPTION_STREAM, args.back().as_string()));
|
||||||
|
|
||||||
|
} catch (torrent::input_error& e) {
|
||||||
|
return apply_obsolete_encryption(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::runtime::network_config()->set_encryption_modes(handshake_mode, stream_mode);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::Object
|
torrent::Object
|
||||||
@@ -220,17 +316,22 @@ initialize_command_network() {
|
|||||||
auto http_stack = torrent::net_thread::http_stack();
|
auto http_stack = torrent::net_thread::http_stack();
|
||||||
auto nw_config = torrent::runtime::network_config();
|
auto nw_config = torrent::runtime::network_config();
|
||||||
|
|
||||||
// Isn't port_open used?
|
CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); });
|
||||||
CMD_VAR_BOOL ("network.port_open", true);
|
CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(value); });
|
||||||
CMD_VAR_BOOL ("network.port_random", true);
|
CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); });
|
||||||
CMD_VAR_STRING ("network.port_range", "6881-6999");
|
CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); });
|
||||||
|
CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); });
|
||||||
|
CMD_ANY_STRING_V("network.listen.port.range.set", [](auto, auto& value) { return set_listen_port_range(value); });
|
||||||
|
CMD_ANY ("network.listen.backlog", [](auto, auto) { return torrent::runtime::network_config()->listen_backlog(); });
|
||||||
|
CMD_ANY_VALUE_V ("network.listen.backlog.set", [](auto, auto& value) { return torrent::runtime::network_config()->set_listen_backlog(value); });
|
||||||
|
|
||||||
CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::listen_port(); });
|
CMD_ANY ("protocol.pex", [](auto, auto) { return torrent::runtime::client_config()->is_pex_enabled(); });
|
||||||
CMD_ANY ("network.listen.backlog", [nw_config](auto, auto) { return nw_config->listen_backlog(); });
|
CMD_ANY_VALUE_V ("protocol.pex.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_pex_enabled(value); });
|
||||||
CMD_ANY_VALUE_V ("network.listen.backlog.set", [nw_config](auto, auto& value) { return nw_config->set_listen_backlog(value); });
|
|
||||||
|
|
||||||
CMD_VAR_BOOL ("protocol.pex", true);
|
CMD_ANY_LIST ("protocol.encryption", [](auto, auto) { return get_encryption(); });
|
||||||
CMD_ANY_LIST ("protocol.encryption.set", [](auto, auto& args) { return apply_encryption(args); });
|
CMD_ANY_LIST ("protocol.encryption.set", [](auto, auto& args) { return apply_encryption(args); });
|
||||||
|
CMD_ANY_LIST ("protocol.encryption.handshake", [](auto, auto) { return get_handshake_encryption(); });
|
||||||
|
CMD_ANY_LIST ("protocol.encryption.stream", [](auto, auto) { return get_stream_encryption(); });
|
||||||
|
|
||||||
CMD_VAR_STRING ("protocol.connection.leech", "leech");
|
CMD_VAR_STRING ("protocol.connection.leech", "leech");
|
||||||
CMD_VAR_STRING ("protocol.connection.seed", "seed");
|
CMD_VAR_STRING ("protocol.connection.seed", "seed");
|
||||||
@@ -301,8 +402,10 @@ initialize_command_network() {
|
|||||||
CMD_ANY ("network.xmlrpc.size_limit", [](auto, auto) { return rpc::rpc.size_limit(); });
|
CMD_ANY ("network.xmlrpc.size_limit", [](auto, auto) { return rpc::rpc.size_limit(); });
|
||||||
CMD_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](auto, auto& arg) { return rpc::rpc.set_size_limit(arg); });
|
CMD_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](auto, auto& arg) { return rpc::rpc.set_size_limit(arg); });
|
||||||
|
|
||||||
CMD_VAR_BOOL ("network.rpc.use_xmlrpc", true);
|
CMD_ANY ("network.rpc.use_xmlrpc", [](auto, auto) { return rpc::rpc.use_xmlrpc(); });
|
||||||
CMD_VAR_BOOL ("network.rpc.use_jsonrpc", true);
|
CMD_ANY_VALUE_V ("network.rpc.use_xmlrpc.set", [](auto, auto& arg) { return rpc::rpc.set_use_xmlrpc(arg); });
|
||||||
|
CMD_ANY ("network.rpc.use_jsonrpc", [](auto, auto) { return rpc::rpc.use_jsonrpc(); });
|
||||||
|
CMD_ANY_VALUE_V ("network.rpc.use_jsonrpc.set", [](auto, auto& arg) { return rpc::rpc.set_use_jsonrpc(arg); });
|
||||||
|
|
||||||
CMD_ANY ("network.block.ipv4", [nw_config](auto, auto) { return nw_config->is_block_ipv4(); });
|
CMD_ANY ("network.block.ipv4", [nw_config](auto, auto) { return nw_config->is_block_ipv4(); });
|
||||||
CMD_ANY_VALUE_V ("network.block.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_block_ipv4(value); });
|
CMD_ANY_VALUE_V ("network.block.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_block_ipv4(value); });
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <iterator>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <torrent/exceptions.h>
|
||||||
|
#include <torrent/object.h>
|
||||||
|
|
||||||
|
#include "rpc/parse.h"
|
||||||
|
#include "rpc/rpc_manager.h"
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
#include "control.h"
|
||||||
|
#include "command_helpers.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const std::string whitespace_characters = " \t\n\r\f\v";
|
||||||
|
|
||||||
|
// The byte offset of every utf-8 character in 'text', terminated by the offset
|
||||||
|
// past the last character. Bytes that are not valid utf-8 lead bytes are
|
||||||
|
// treated as single characters.
|
||||||
|
std::vector<size_t>
|
||||||
|
utf8_offsets(const std::string& text) {
|
||||||
|
std::vector<size_t> offsets;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < text.size(); i++)
|
||||||
|
if (i == 0 || (static_cast<unsigned char>(text[i]) & 0xC0) != 0x80)
|
||||||
|
offsets.push_back(i);
|
||||||
|
|
||||||
|
offsets.push_back(text.size());
|
||||||
|
return offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t
|
||||||
|
utf8_length(const std::string& text) {
|
||||||
|
int64_t result = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < text.size(); i++)
|
||||||
|
if (i == 0 || (static_cast<unsigned char>(text[i]) & 0xC0) != 0x80)
|
||||||
|
result++;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
utf8_substr(const std::string& text, const std::vector<size_t>& offsets, size_t first, size_t last) {
|
||||||
|
return text.substr(offsets[first], offsets[last] - offsets[first]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string>
|
||||||
|
utf8_character_set(const std::string& text) {
|
||||||
|
auto offsets = utf8_offsets(text);
|
||||||
|
std::set<std::string> result;
|
||||||
|
|
||||||
|
for (size_t i = 0; i + 1 < offsets.size(); i++)
|
||||||
|
result.insert(utf8_substr(text, offsets, i, i + 1));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
ascii_lowercase(std::string text) {
|
||||||
|
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A 'max_count' of zero means the command accepts any number of arguments.
|
||||||
|
void
|
||||||
|
verify_argument_count(const char* name, const torrent::Object::list_type& args, size_t min_count, size_t max_count) {
|
||||||
|
if (args.size() < min_count || (max_count != 0 && args.size() > max_count))
|
||||||
|
throw torrent::input_error(std::string(name) + ": invalid number of arguments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const torrent::Object&
|
||||||
|
argument_at(const torrent::Object::list_type& args, size_t index) {
|
||||||
|
return *std::next(args.begin(), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
flatten_argument(const torrent::Object& arg, std::vector<std::string>* dest) {
|
||||||
|
if (!arg.is_list()) {
|
||||||
|
dest->push_back(rpc::convert_to_string(arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& child : arg.as_list())
|
||||||
|
flatten_argument(child, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The {old, new} pairs shared by 'string.map' and 'string.replace'.
|
||||||
|
std::pair<std::string, std::string>
|
||||||
|
argument_to_pair(const char* name, const torrent::Object& arg) {
|
||||||
|
if (!arg.is_list() || arg.as_list().size() != 2)
|
||||||
|
throw torrent::input_error(std::string(name) + ": arguments after the text must be {old, new} pairs.");
|
||||||
|
|
||||||
|
return {rpc::convert_to_string(arg.as_list().front()), rpc::convert_to_string(arg.as_list().back())};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the first argument against every remaining argument, returning true
|
||||||
|
// as soon as one of them matches.
|
||||||
|
torrent::Object
|
||||||
|
apply_string_predicate(const char* name, const torrent::Object::list_type& args, bool (*predicate)(const std::string&, const std::string&)) {
|
||||||
|
verify_argument_count(name, args, 2, 0);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
|
||||||
|
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
|
||||||
|
if (predicate(text, rpc::convert_to_string(*itr)))
|
||||||
|
return int64_t(1);
|
||||||
|
|
||||||
|
return int64_t(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
text_equals(const std::string& text, const std::string& other) {
|
||||||
|
return text == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
text_starts_with(const std::string& text, const std::string& prefix) {
|
||||||
|
return text.size() >= prefix.size() && text.compare(0, prefix.size(), prefix) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
text_ends_with(const std::string& text, const std::string& tail) {
|
||||||
|
return text.size() >= tail.size() && text.compare(text.size() - tail.size(), tail.size(), tail) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
text_contains(const std::string& text, const std::string& needle) {
|
||||||
|
return text.find(needle) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
text_contains_i(const std::string& text, const std::string& needle) {
|
||||||
|
return ascii_lowercase(text).find(ascii_lowercase(needle)) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_length(const torrent::Object::list_type& args) {
|
||||||
|
verify_argument_count("string.length", args, 1, 1);
|
||||||
|
|
||||||
|
return utf8_length(rpc::convert_to_string(args.front()));
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_substr(const torrent::Object::list_type& args) {
|
||||||
|
verify_argument_count("string.substr", args, 1, 4);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
auto offsets = utf8_offsets(text);
|
||||||
|
auto text_length = static_cast<int64_t>(offsets.size() - 1);
|
||||||
|
|
||||||
|
auto position = args.size() > 1 ? rpc::convert_to_value(argument_at(args, 1)) : 0;
|
||||||
|
auto fallback = args.size() > 3 ? rpc::convert_to_string(argument_at(args, 3)) : std::string();
|
||||||
|
|
||||||
|
// Negative positions are relative to the end of the string.
|
||||||
|
if (position < 0)
|
||||||
|
position += text_length;
|
||||||
|
|
||||||
|
if (position < 0 || position > text_length)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
auto last = text_length;
|
||||||
|
|
||||||
|
if (args.size() > 2) {
|
||||||
|
auto count = rpc::convert_to_value(argument_at(args, 2));
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
throw torrent::input_error("string.substr: the character count cannot be negative.");
|
||||||
|
|
||||||
|
last = std::min(text_length, position + std::min(count, text_length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf8_substr(text, offsets, position, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_split(const torrent::Object::list_type& args) {
|
||||||
|
verify_argument_count("string.split", args, 2, 2);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
auto delim = rpc::convert_to_string(args.back());
|
||||||
|
auto result = torrent::Object::create_list();
|
||||||
|
|
||||||
|
// An empty delimiter splits the text into its utf-8 characters.
|
||||||
|
if (delim.empty()) {
|
||||||
|
auto offsets = utf8_offsets(text);
|
||||||
|
|
||||||
|
for (size_t i = 0; i + 1 < offsets.size(); i++)
|
||||||
|
result.as_list().push_back(utf8_substr(text, offsets, i, i + 1));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t first = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
auto pos = text.find(delim, first);
|
||||||
|
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
result.as_list().push_back(text.substr(first));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.as_list().push_back(text.substr(first, pos - first));
|
||||||
|
first = pos + delim.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_join(const torrent::Object::list_type& args) {
|
||||||
|
verify_argument_count("string.join", args, 1, 0);
|
||||||
|
|
||||||
|
auto delim = rpc::convert_to_string(args.front());
|
||||||
|
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
|
||||||
|
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
|
||||||
|
flatten_argument(*itr, &parts);
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (auto itr = parts.begin(); itr != parts.end(); itr++) {
|
||||||
|
if (itr != parts.begin())
|
||||||
|
result += delim;
|
||||||
|
|
||||||
|
result += *itr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_pad(const char* name, const torrent::Object::list_type& args, bool pad_start) {
|
||||||
|
verify_argument_count(name, args, 2, 3);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
auto pad_size = rpc::convert_to_value(argument_at(args, 1));
|
||||||
|
auto padding = args.size() > 2 ? rpc::convert_to_string(argument_at(args, 2)) : std::string(" ");
|
||||||
|
|
||||||
|
auto text_length = utf8_length(text);
|
||||||
|
|
||||||
|
if (pad_size <= text_length || padding.empty())
|
||||||
|
return text;
|
||||||
|
|
||||||
|
auto padding_offsets = utf8_offsets(padding);
|
||||||
|
auto padding_length = static_cast<int64_t>(padding_offsets.size() - 1);
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (int64_t i = 0; i < pad_size - text_length; i++) {
|
||||||
|
auto index = static_cast<size_t>(i % padding_length);
|
||||||
|
result += utf8_substr(padding, padding_offsets, index, index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pad_start ? result + text : text + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_strip(const char* name, const torrent::Object::list_type& args, bool strip_start, bool strip_end) {
|
||||||
|
verify_argument_count(name, args, 1, 0);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
|
||||||
|
std::string strippable;
|
||||||
|
|
||||||
|
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
|
||||||
|
strippable += rpc::convert_to_string(*itr);
|
||||||
|
|
||||||
|
if (args.size() == 1)
|
||||||
|
strippable = whitespace_characters;
|
||||||
|
|
||||||
|
auto characters = utf8_character_set(strippable);
|
||||||
|
auto offsets = utf8_offsets(text);
|
||||||
|
|
||||||
|
size_t first = 0;
|
||||||
|
size_t last = offsets.size() - 1;
|
||||||
|
|
||||||
|
while (strip_start && first < last && characters.count(utf8_substr(text, offsets, first, first + 1)) != 0)
|
||||||
|
first++;
|
||||||
|
|
||||||
|
while (strip_end && last > first && characters.count(utf8_substr(text, offsets, last - 1, last)) != 0)
|
||||||
|
last--;
|
||||||
|
|
||||||
|
return utf8_substr(text, offsets, first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_map(const torrent::Object::list_type& args) {
|
||||||
|
verify_argument_count("string.map", args, 2, 0);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
|
||||||
|
for (auto itr = std::next(args.begin()); itr != args.end(); itr++) {
|
||||||
|
auto pair = argument_to_pair("string.map", *itr);
|
||||||
|
|
||||||
|
if (text == pair.first)
|
||||||
|
return pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
apply_string_replace(const torrent::Object::list_type& args) {
|
||||||
|
verify_argument_count("string.replace", args, 2, 0);
|
||||||
|
|
||||||
|
auto text = rpc::convert_to_string(args.front());
|
||||||
|
|
||||||
|
for (auto itr = std::next(args.begin()); itr != args.end(); itr++) {
|
||||||
|
auto pair = argument_to_pair("string.replace", *itr);
|
||||||
|
|
||||||
|
if (pair.first.empty())
|
||||||
|
throw torrent::input_error("string.replace: the replaced text cannot be empty.");
|
||||||
|
|
||||||
|
for (auto pos = text.find(pair.first); pos != std::string::npos; pos = text.find(pair.first, pos + pair.second.size()))
|
||||||
|
text.replace(pos, pair.first.size(), pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
initialize_command_string() {
|
||||||
|
// clang-format off
|
||||||
|
CMD2_ANY_LIST("string.length", [](auto, const auto& args) { return apply_string_length(args); });
|
||||||
|
CMD2_ANY_LIST("string.substr", [](auto, const auto& args) { return apply_string_substr(args); });
|
||||||
|
CMD2_ANY_LIST("string.split", [](auto, const auto& args) { return apply_string_split(args); });
|
||||||
|
CMD2_ANY_LIST("string.join", [](auto, const auto& args) { return apply_string_join(args); });
|
||||||
|
CMD2_ANY_LIST("string.map", [](auto, const auto& args) { return apply_string_map(args); });
|
||||||
|
CMD2_ANY_LIST("string.replace", [](auto, const auto& args) { return apply_string_replace(args); });
|
||||||
|
|
||||||
|
CMD2_ANY_LIST("string.equals", [](auto, const auto& args) { return apply_string_predicate("string.equals", args, &text_equals); });
|
||||||
|
CMD2_ANY_LIST("string.starts_with", [](auto, const auto& args) { return apply_string_predicate("string.starts_with", args, &text_starts_with); });
|
||||||
|
CMD2_ANY_LIST("string.ends_with", [](auto, const auto& args) { return apply_string_predicate("string.ends_with", args, &text_ends_with); });
|
||||||
|
CMD2_ANY_LIST("string.contains", [](auto, const auto& args) { return apply_string_predicate("string.contains", args, &text_contains); });
|
||||||
|
CMD2_ANY_LIST("string.contains_i", [](auto, const auto& args) { return apply_string_predicate("string.contains_i", args, &text_contains_i); });
|
||||||
|
|
||||||
|
CMD2_ANY_LIST("string.lpad", [](auto, const auto& args) { return apply_string_pad("string.lpad", args, true); });
|
||||||
|
CMD2_ANY_LIST("string.rpad", [](auto, const auto& args) { return apply_string_pad("string.rpad", args, false); });
|
||||||
|
|
||||||
|
CMD2_ANY_LIST("string.strip", [](auto, const auto& args) { return apply_string_strip("string.strip", args, true, true); });
|
||||||
|
CMD2_ANY_LIST("string.lstrip", [](auto, const auto& args) { return apply_string_strip("string.lstrip", args, true, false); });
|
||||||
|
CMD2_ANY_LIST("string.rstrip", [](auto, const auto& args) { return apply_string_strip("string.rstrip", args, false, true); });
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
for (const auto name : {"string.length", "string.substr", "string.split", "string.join", "string.map", "string.replace",
|
||||||
|
"string.equals", "string.starts_with", "string.ends_with", "string.contains", "string.contains_i",
|
||||||
|
"string.lpad", "string.rpad", "string.strip", "string.lstrip", "string.rstrip"})
|
||||||
|
rpc::rpc.mark_safe(name);
|
||||||
|
}
|
||||||
@@ -142,7 +142,7 @@ initialize_command_tracker() {
|
|||||||
lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0);
|
lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0);
|
||||||
});
|
});
|
||||||
CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); });
|
CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); });
|
||||||
CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_config()->set_override_dht_port(value); });
|
CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(value); });
|
||||||
|
|
||||||
CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); });
|
CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); });
|
||||||
CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); });
|
CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); });
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ Control::initialize() {
|
|||||||
display::Window::slot_unschedule([this](display::Window* w) { m_display->unschedule(w); });
|
display::Window::slot_unschedule([this](display::Window* w) { m_display->unschedule(w); });
|
||||||
display::Window::slot_adjust([this]() { m_display->adjust_layout(); });
|
display::Window::slot_adjust([this]() { m_display->adjust_layout(); });
|
||||||
|
|
||||||
torrent::net_thread::http_stack()->set_user_agent(USER_AGENT);
|
|
||||||
|
|
||||||
m_core->listen_open();
|
|
||||||
m_core->set_hashing_view(*m_view_manager->find_throw("hashing"));
|
m_core->set_hashing_view(*m_view_manager->find_throw("hashing"));
|
||||||
|
|
||||||
m_ui->init(this);
|
m_ui->init(this);
|
||||||
|
|||||||
+3
-3
@@ -6,7 +6,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <torrent/torrent.h>
|
#include <torrent/torrent.h>
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
class Root;
|
class Root;
|
||||||
@@ -107,8 +107,8 @@ private:
|
|||||||
mode_t m_umask;
|
mode_t m_umask;
|
||||||
std::string m_workingDirectory;
|
std::string m_workingDirectory;
|
||||||
|
|
||||||
torrent::utils::SchedulerEntry m_task_shutdown;
|
torrent::system::SchedulerEntry m_task_shutdown;
|
||||||
torrent::utils::SchedulerEntry m_task_shutdown_clear_requests;
|
torrent::system::SchedulerEntry m_task_shutdown_clear_requests;
|
||||||
|
|
||||||
int m_clear_requests_count{};
|
int m_clear_requests_count{};
|
||||||
|
|
||||||
|
|||||||
+21
-7
@@ -8,6 +8,7 @@
|
|||||||
#include <torrent/object_stream.h>
|
#include <torrent/object_stream.h>
|
||||||
#include <torrent/rate.h>
|
#include <torrent/rate.h>
|
||||||
#include <torrent/runtime/network_manager.h>
|
#include <torrent/runtime/network_manager.h>
|
||||||
|
#include <torrent/runtime/runtime.h>
|
||||||
#include <torrent/tracker/dht_controller.h>
|
#include <torrent/tracker/dht_controller.h>
|
||||||
#include <torrent/utils/log.h>
|
#include <torrent/utils/log.h>
|
||||||
|
|
||||||
@@ -141,18 +142,29 @@ DhtManager::save_dht_cache() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
DhtManager::set_mode_by_user(const std::string& arg) {
|
DhtManager::set_mode_by_user(const std::string& arg) {
|
||||||
for (int i = 0; i < dht_settings_num; i++) {
|
unsigned int mode = [arg]() {
|
||||||
if (arg == dht_settings[i]) {
|
for (int i = 0; i < dht_settings_num; i++) {
|
||||||
m_set_by_user = true;
|
if (arg == dht_settings[i])
|
||||||
return set_mode_directly(i);
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw torrent::input_error("Invalid dht mode: " + arg);
|
||||||
|
}();
|
||||||
|
|
||||||
|
m_set_by_user = true;
|
||||||
|
|
||||||
|
if (!torrent::runtime::is_network_initialized()) {
|
||||||
|
m_start = mode;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_mode_directly(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DhtManager::set_mode_directly(unsigned int mode) {
|
DhtManager::set_mode_directly(unsigned int mode) {
|
||||||
if (mode >= dht_settings_num)
|
if (mode >= dht_settings_num)
|
||||||
throw torrent::input_error("Invalid argument.");
|
throw torrent::input_error("Invalid dht mode.");
|
||||||
|
|
||||||
m_start = mode;
|
m_start = mode;
|
||||||
|
|
||||||
@@ -164,8 +176,10 @@ DhtManager::set_mode_directly(unsigned int mode) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
DhtManager::set_auto_if_untouched_and_has_session() {
|
DhtManager::set_auto_if_untouched_and_has_session() {
|
||||||
if (m_set_by_user)
|
if (m_set_by_user) {
|
||||||
|
set_mode_directly(m_start);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (rpc::call_command_string("session.path").empty()) {
|
if (rpc::call_command_string("session.path").empty()) {
|
||||||
LT_LOG("DHT auto-start disabled, session path not set.", 0);
|
LT_LOG("DHT auto-start disabled, session path not set.", 0);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#define RTORRENT_CORE_DHT_MANAGER_H
|
#define RTORRENT_CORE_DHT_MANAGER_H
|
||||||
|
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -42,8 +42,8 @@ private:
|
|||||||
uint64_t m_dhtPrevBytesUp;
|
uint64_t m_dhtPrevBytesUp;
|
||||||
uint64_t m_dhtPrevBytesDown;
|
uint64_t m_dhtPrevBytesDown;
|
||||||
|
|
||||||
torrent::utils::SchedulerEntry m_update_timeout;
|
torrent::system::SchedulerEntry m_update_timeout;
|
||||||
torrent::utils::SchedulerEntry m_stop_timeout;
|
torrent::system::SchedulerEntry m_stop_timeout;
|
||||||
|
|
||||||
bool m_warned{};
|
bool m_warned{};
|
||||||
bool m_set_by_user{};
|
bool m_set_by_user{};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <torrent/rate.h>
|
#include <torrent/rate.h>
|
||||||
#include <torrent/data/file_utils.h>
|
#include <torrent/data/file_utils.h>
|
||||||
#include <torrent/net/http_stack.h>
|
#include <torrent/net/http_stack.h>
|
||||||
|
#include <torrent/runtime/client_config.h>
|
||||||
#include <torrent/utils/string_manip.h>
|
#include <torrent/utils/string_manip.h>
|
||||||
|
|
||||||
#include "control.h"
|
#include "control.h"
|
||||||
@@ -34,8 +35,10 @@ is_network_uri(const std::string& uri) {
|
|||||||
std::strncmp(uri.c_str(), "ftp://", 6) == 0;
|
std::strncmp(uri.c_str(), "ftp://", 6) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr const char* session_invalid_message = "Session data is invalid, ignoring it";
|
||||||
|
|
||||||
static std::unique_ptr<torrent::Object>
|
static std::unique_ptr<torrent::Object>
|
||||||
download_factory_load_stream(const char* filename) {
|
download_factory_load_stream(const char* filename, bool* is_invalid) {
|
||||||
std::fstream stream(filename, std::ios::in | std::ios::binary);
|
std::fstream stream(filename, std::ios::in | std::ios::binary);
|
||||||
|
|
||||||
if (!stream.is_open())
|
if (!stream.is_open())
|
||||||
@@ -44,8 +47,10 @@ download_factory_load_stream(const char* filename) {
|
|||||||
auto obj = std::make_unique<torrent::Object>();
|
auto obj = std::make_unique<torrent::Object>();
|
||||||
stream >> *obj;
|
stream >> *obj;
|
||||||
|
|
||||||
if (!stream.good())
|
if (!stream.good() || !obj->is_map()) {
|
||||||
|
*is_invalid = true;
|
||||||
return std::unique_ptr<torrent::Object>();
|
return std::unique_ptr<torrent::Object>();
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -104,40 +109,42 @@ DownloadFactory::receive_load() {
|
|||||||
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
|
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
|
||||||
|
|
||||||
if (is_network_uri(m_uri)) {
|
if (is_network_uri(m_uri)) {
|
||||||
// Http handling here.
|
|
||||||
m_stream.reset(new std::stringstream);
|
m_stream.reset(new std::stringstream);
|
||||||
|
|
||||||
m_manager->http_queue()->insert(m_uri, m_stream,
|
auto done_fn = [this]() { receive_loaded(); };
|
||||||
[this]() { receive_loaded(); },
|
auto failed_fn = [this](const std::string& error) { receive_failed(error); };
|
||||||
[this](const std::string& error) { receive_failed(error); }
|
|
||||||
);
|
m_manager->http_queue()->insert(m_uri, m_stream, done_fn, failed_fn);
|
||||||
|
|
||||||
m_variables["tied_to_file"] = (int64_t)false;
|
m_variables["tied_to_file"] = (int64_t)false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (is_magnet_uri(m_uri)) {
|
if (is_magnet_uri(m_uri)) {
|
||||||
// DEBUG: Use m_object.
|
// DEBUG: Use m_object.
|
||||||
m_stream.reset(new std::stringstream());
|
m_stream.reset(new std::stringstream());
|
||||||
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e";
|
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e";
|
||||||
|
|
||||||
m_variables["tied_to_file"] = (int64_t)false;
|
m_variables["tied_to_file"] = (int64_t)false;
|
||||||
receive_loaded();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
std::fstream stream(expand_path(m_uri).c_str(), std::ios::in | std::ios::binary);
|
|
||||||
|
|
||||||
if (!stream.is_open())
|
|
||||||
return receive_failed("Could not open file");
|
|
||||||
|
|
||||||
m_object = new torrent::Object;
|
|
||||||
stream >> *m_object;
|
|
||||||
|
|
||||||
if (!stream.good())
|
|
||||||
return receive_failed("Reading torrent file failed");
|
|
||||||
|
|
||||||
m_isFile = true;
|
|
||||||
|
|
||||||
receive_loaded();
|
receive_loaded();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::fstream stream(expand_path(m_uri).c_str(), std::ios::in | std::ios::binary);
|
||||||
|
|
||||||
|
if (!stream.is_open())
|
||||||
|
return receive_failed("Could not open file");
|
||||||
|
|
||||||
|
m_object = new torrent::Object;
|
||||||
|
stream >> *m_object;
|
||||||
|
|
||||||
|
if (!stream.good())
|
||||||
|
return receive_failed("Reading torrent file failed");
|
||||||
|
|
||||||
|
m_isFile = true;
|
||||||
|
|
||||||
|
receive_loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -158,8 +165,13 @@ DownloadFactory::receive_commit() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
DownloadFactory::receive_success() {
|
DownloadFactory::receive_success() {
|
||||||
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str());
|
bool session_invalid = false;
|
||||||
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str());
|
|
||||||
|
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
|
||||||
|
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
|
||||||
|
|
||||||
|
if (session_invalid)
|
||||||
|
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
|
||||||
|
|
||||||
uint32_t tracker_key;
|
uint32_t tracker_key;
|
||||||
|
|
||||||
@@ -181,6 +193,14 @@ DownloadFactory::receive_success() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (session_invalid) {
|
||||||
|
download->set_hash_failed(true);
|
||||||
|
download->set_message(session_invalid_message);
|
||||||
|
|
||||||
|
if (m_printLog)
|
||||||
|
m_manager->push_log_std(std::string(session_invalid_message) + ": \"" + m_uri + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
torrent::Object* root = download->bencode();
|
torrent::Object* root = download->bencode();
|
||||||
|
|
||||||
if (download->download()->info()->is_meta_download()) {
|
if (download->download()->info()->is_meta_download()) {
|
||||||
@@ -269,11 +289,24 @@ DownloadFactory::receive_success() {
|
|||||||
if (!m_session && m_variables["tied_to_file"].as_value())
|
if (!m_session && m_variables["tied_to_file"].as_value())
|
||||||
rpc::call_command("d.tied_to_file.set", m_uri.empty() ? m_variables["tied_file"] : m_uri, rpc::make_target(download));
|
rpc::call_command("d.tied_to_file.set", m_uri.empty() ? m_variables["tied_file"] : m_uri, rpc::make_target(download));
|
||||||
|
|
||||||
rpc::call_command("d.peer_exchange.set", rpc::call_command_value("protocol.pex"), rpc::make_target(download));
|
rpc::call_command("d.peer_exchange.set", torrent::runtime::client_config()->is_pex_enabled(), rpc::make_target(download));
|
||||||
|
|
||||||
torrent::resume_load_addresses(*download->download(), resumeObject);
|
try {
|
||||||
torrent::resume_load_file_priorities(*download->download(), resumeObject);
|
torrent::resume_load_addresses(*download->download(), resumeObject);
|
||||||
torrent::resume_load_tracker_settings(*download->download(), resumeObject);
|
torrent::resume_load_file_priorities(*download->download(), resumeObject);
|
||||||
|
torrent::resume_load_tracker_settings(*download->download(), resumeObject);
|
||||||
|
|
||||||
|
} catch (const torrent::input_error& e) {
|
||||||
|
std::string msg = std::string(session_invalid_message) + ": " + e.what();
|
||||||
|
|
||||||
|
lt_log_print(torrent::LOG_ERROR, "%s: %s", msg.c_str(), m_uri.c_str());
|
||||||
|
|
||||||
|
if (m_printLog)
|
||||||
|
m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
|
||||||
|
|
||||||
|
download->set_hash_failed(true);
|
||||||
|
download->set_message(msg);
|
||||||
|
}
|
||||||
|
|
||||||
// The action of inserting might cause the torrent to be
|
// The action of inserting might cause the torrent to be
|
||||||
// opened/started or such. Figure out a nicer way of handling this.
|
// opened/started or such. Figure out a nicer way of handling this.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
#include "http_queue.h"
|
#include "http_queue.h"
|
||||||
|
|
||||||
@@ -79,9 +79,9 @@ private:
|
|||||||
command_list_type m_commands;
|
command_list_type m_commands;
|
||||||
torrent::Object::map_type m_variables;
|
torrent::Object::map_type m_variables;
|
||||||
|
|
||||||
slot_void m_slot_finished;
|
slot_void m_slot_finished;
|
||||||
torrent::utils::SchedulerEntry m_task_load;
|
torrent::system::SchedulerEntry m_task_load;
|
||||||
torrent::utils::SchedulerEntry m_task_commit;
|
torrent::system::SchedulerEntry m_task_commit;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool is_network_uri(const std::string& uri);
|
bool is_network_uri(const std::string& uri);
|
||||||
|
|||||||
@@ -19,14 +19,11 @@ HttpQueue::insert(const std::string& url, std::shared_ptr<std::ostream> stream,
|
|||||||
for (auto& slot : m_signal_insert)
|
for (auto& slot : m_signal_insert)
|
||||||
slot(*itr);
|
slot(*itr);
|
||||||
|
|
||||||
itr->add_done_slot(torrent::this_thread::thread(), [this, itr]() { erase(itr); });
|
|
||||||
itr->add_failed_slot(torrent::this_thread::thread(), [this, itr](auto) { erase(itr); });
|
|
||||||
|
|
||||||
itr->add_done_slot(torrent::this_thread::thread(), std::move(done_fn));
|
itr->add_done_slot(torrent::this_thread::thread(), std::move(done_fn));
|
||||||
itr->add_failed_slot(torrent::this_thread::thread(), std::move(failed_fn));
|
itr->add_done_slot(torrent::this_thread::thread(), [this, itr]() { erase(itr); });
|
||||||
|
|
||||||
// TODO: Downloading http torrents doesn't seem to work.
|
itr->add_failed_slot(torrent::this_thread::thread(), std::move(failed_fn));
|
||||||
// TODO: Quitting no longer works.
|
itr->add_failed_slot(torrent::this_thread::thread(), [this, itr](auto) { erase(itr); });
|
||||||
|
|
||||||
torrent::net_thread::http_stack()->start_get(*itr);
|
torrent::net_thread::http_stack()->start_get(*itr);
|
||||||
|
|
||||||
|
|||||||
+2
-34
@@ -33,6 +33,8 @@
|
|||||||
#include "core/http_queue.h"
|
#include "core/http_queue.h"
|
||||||
#include "core/view.h"
|
#include "core/view.h"
|
||||||
|
|
||||||
|
#include <torrent/runtime/client_config.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
const int Manager::create_start;
|
const int Manager::create_start;
|
||||||
@@ -160,40 +162,6 @@ Manager::shutdown(bool force) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
Manager::listen_open() {
|
|
||||||
// This stuff really should be moved outside of manager, make it
|
|
||||||
// part of the init script.
|
|
||||||
if (!rpc::call_command_value("network.port_open"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
int portFirst, portLast;
|
|
||||||
torrent::Object portRange = rpc::call_command("network.port_range");
|
|
||||||
|
|
||||||
if (!portRange.is_string())
|
|
||||||
throw torrent::input_error("Invalid port_range argument type.");
|
|
||||||
|
|
||||||
if (std::sscanf(portRange.as_string().c_str(), "%i-%i", &portFirst, &portLast) != 2)
|
|
||||||
throw torrent::input_error("Invalid port_range argument.");
|
|
||||||
|
|
||||||
if (portFirst > portLast || portLast >= (1 << 16))
|
|
||||||
throw torrent::input_error("Invalid port range.");
|
|
||||||
|
|
||||||
if (rpc::call_command_value("network.port_random")) {
|
|
||||||
int boundary = portFirst + random() % (portLast - portFirst + 1);
|
|
||||||
|
|
||||||
if (torrent::runtime::network_manager()->listen_open(boundary, portLast) ||
|
|
||||||
torrent::runtime::network_manager()->listen_open(portFirst, boundary))
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (torrent::runtime::network_manager()->listen_open(portFirst, portLast))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw torrent::input_error("Could not open/bind port for listening: " + std::string(std::strerror(errno)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Manager::receive_http_failed(std::string msg) {
|
Manager::receive_http_failed(std::string msg) {
|
||||||
push_log_std("Http download error: \"" + msg + "\"");
|
push_log_std("Http download error: \"" + msg + "\"");
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ public:
|
|||||||
|
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
void listen_open();
|
|
||||||
|
|
||||||
const std::string& magnet_path();
|
const std::string& magnet_path();
|
||||||
void set_magnet_path(const std::string& path);
|
void set_magnet_path(const std::string& path);
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -18,7 +18,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ private:
|
|||||||
|
|
||||||
std::chrono::microseconds m_last_changed{};
|
std::chrono::microseconds m_last_changed{};
|
||||||
|
|
||||||
signal_void m_signal_changed;
|
signal_void m_signal_changed;
|
||||||
torrent::utils::SchedulerEntry m_delay_changed;
|
torrent::system::SchedulerEntry m_delay_changed;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef RTORRENT_DISPLAY_MANAGER_H
|
#ifndef RTORRENT_DISPLAY_MANAGER_H
|
||||||
#define RTORRENT_DISPLAY_MANAGER_H
|
#define RTORRENT_DISPLAY_MANAGER_H
|
||||||
|
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
#include "display/frame.h"
|
#include "display/frame.h"
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ private:
|
|||||||
bool m_force_redraw{false};
|
bool m_force_redraw{false};
|
||||||
Frame m_root_frame;
|
Frame m_root_frame;
|
||||||
|
|
||||||
std::chrono::microseconds m_time_last_update{};
|
std::chrono::microseconds m_time_last_update{};
|
||||||
torrent::utils::ExternalScheduler m_scheduler;
|
torrent::system::ExternalScheduler m_scheduler;
|
||||||
torrent::utils::SchedulerEntry m_task_update;
|
torrent::system::SchedulerEntry m_task_update;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ print_status_info(char* first, char* last) {
|
|||||||
if (!torrent::up_throttle_global()->is_throttled()) {
|
if (!torrent::up_throttle_global()->is_throttled()) {
|
||||||
first = print_buffer(first, last, "[Throttle off");
|
first = print_buffer(first, last, "[Throttle off");
|
||||||
} else {
|
} else {
|
||||||
first = print_buffer(first, last, "[Throttle %3i", torrent::up_throttle_global()->max_rate() / 1024);
|
first = print_buffer(first, last, "[Throttle %3i", (int)(torrent::up_throttle_global()->max_rate() / 1024));
|
||||||
|
|
||||||
if (!throttle_up_names.empty())
|
if (!throttle_up_names.empty())
|
||||||
first = print_status_throttle_limit(first, last, true, throttle_up_names);
|
first = print_status_throttle_limit(first, last, true, throttle_up_names);
|
||||||
@@ -354,7 +354,7 @@ print_status_info(char* first, char* last) {
|
|||||||
if (!torrent::down_throttle_global()->is_throttled()) {
|
if (!torrent::down_throttle_global()->is_throttled()) {
|
||||||
first = print_buffer(first, last, " / off KB]");
|
first = print_buffer(first, last, " / off KB]");
|
||||||
} else {
|
} else {
|
||||||
first = print_buffer(first, last, " / %3i", torrent::down_throttle_global()->max_rate() / 1024);
|
first = print_buffer(first, last, " / %3i", (int)(torrent::down_throttle_global()->max_rate() / 1024));
|
||||||
|
|
||||||
if (!throttle_down_names.empty())
|
if (!throttle_down_names.empty())
|
||||||
first = print_status_throttle_limit(first, last, false, throttle_down_names);
|
first = print_status_throttle_limit(first, last, false, throttle_down_names);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#define RTORRENT_WINDOW_BASE_H
|
#define RTORRENT_WINDOW_BASE_H
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
#include <torrent/system/thread.h>
|
#include <torrent/system/thread.h>
|
||||||
|
|
||||||
#include "canvas.h"
|
#include "canvas.h"
|
||||||
@@ -99,7 +99,7 @@ protected:
|
|||||||
extent_type m_max_width;
|
extent_type m_max_width;
|
||||||
extent_type m_max_height;
|
extent_type m_max_height;
|
||||||
|
|
||||||
torrent::utils::SchedulerEntry m_task_update;
|
torrent::system::SchedulerEntry m_task_update;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return a range with a distance of no more than __distance and
|
// Return a range with a distance of no more than __distance and
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ private:
|
|||||||
signal_curl_get::iterator m_conn_insert;
|
signal_curl_get::iterator m_conn_insert;
|
||||||
signal_curl_get::iterator m_conn_erase;
|
signal_curl_get::iterator m_conn_erase;
|
||||||
|
|
||||||
torrent::utils::SchedulerEntry m_task_deactivate;
|
torrent::system::SchedulerEntry m_task_deactivate;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#ifndef RTORRENT_DISPLAY_WINDOW_LOG_H
|
#ifndef RTORRENT_DISPLAY_WINDOW_LOG_H
|
||||||
#define RTORRENT_DISPLAY_WINDOW_LOG_H
|
#define RTORRENT_DISPLAY_WINDOW_LOG_H
|
||||||
|
|
||||||
|
#include <torrent/system/scheduler.h>
|
||||||
#include <torrent/utils/log_buffer.h>
|
#include <torrent/utils/log_buffer.h>
|
||||||
#include <torrent/utils/scheduler.h>
|
|
||||||
|
|
||||||
#include "window.h"
|
#include "window.h"
|
||||||
|
|
||||||
@@ -22,10 +22,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
inline iterator find_older();
|
inline iterator find_older();
|
||||||
|
|
||||||
torrent::log_buffer* m_log;
|
torrent::log_buffer* m_log;
|
||||||
torrent::utils::SchedulerEntry m_task_update;
|
torrent::system::SchedulerEntry m_task_update;
|
||||||
|
|
||||||
align_cacheline std::atomic<bool> m_log_updating{};
|
align_cacheline
|
||||||
|
|
||||||
|
std::atomic<bool> m_log_updating{};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <torrent/exceptions.h>
|
#include <torrent/exceptions.h>
|
||||||
|
|
||||||
rpc::ip_table_list ip_tables;
|
rpc::ip_table_list ip_tables;
|
||||||
@@ -28,3 +29,32 @@ expand_path(const std::string& path) {
|
|||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolves a path to a canonical one with no symlinks or relative components,
|
||||||
|
// so it can safely be handed to an external script. Returns an empty string if
|
||||||
|
// the path does not name an existing file or directory.
|
||||||
|
std::string
|
||||||
|
resolve_path(const std::string& path) {
|
||||||
|
if (path.empty())
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
char* resolved = ::realpath(expand_path(path).c_str(), nullptr);
|
||||||
|
|
||||||
|
if (resolved == nullptr)
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
std::string result(resolved);
|
||||||
|
std::free(resolved);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
resolve_path_or_throw(const std::string& path) {
|
||||||
|
auto result = resolve_path(path);
|
||||||
|
|
||||||
|
if (result.empty())
|
||||||
|
throw torrent::input_error("Could not resolve path: '" + path + "'.");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ extern rpc::ip_table_list ip_tables;
|
|||||||
extern Control* control;
|
extern Control* control;
|
||||||
|
|
||||||
std::string expand_path(const std::string& path);
|
std::string expand_path(const std::string& path);
|
||||||
|
std::string resolve_path(const std::string& path);
|
||||||
|
std::string resolve_path_or_throw(const std::string& path);
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
class SCgi;
|
class SCgi;
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ namespace input {
|
|||||||
|
|
||||||
void
|
void
|
||||||
InputEvent::insert() {
|
InputEvent::insert() {
|
||||||
torrent::this_thread::poll()->open(this);
|
torrent::this_thread::poll()->open_and_insert_read(this);
|
||||||
torrent::this_thread::poll()->insert_read(this);
|
|
||||||
torrent::this_thread::poll()->insert_error(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -2,16 +2,15 @@
|
|||||||
#define RTORRENT_INPUT_INPUT_EVENT_H
|
#define RTORRENT_INPUT_INPUT_EVENT_H
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <torrent/system/event.h>
|
||||||
#include <torrent/event.h>
|
|
||||||
|
|
||||||
namespace input {
|
namespace input {
|
||||||
|
|
||||||
class InputEvent : public torrent::Event {
|
class InputEvent : public torrent::system::Event {
|
||||||
public:
|
public:
|
||||||
typedef std::function<void (int)> slot_int;
|
typedef std::function<void (int)> slot_int;
|
||||||
|
|
||||||
InputEvent(int fd) { m_fileDesc = fd; }
|
InputEvent(int fd) { set_file_descriptor(fd); }
|
||||||
|
|
||||||
const char* type_name() const override { return "input"; }
|
const char* type_name() const override { return "input"; }
|
||||||
|
|
||||||
|
|||||||
+44
-26
@@ -11,6 +11,9 @@
|
|||||||
#include <torrent/exceptions.h>
|
#include <torrent/exceptions.h>
|
||||||
#include <torrent/data/chunk_utils.h>
|
#include <torrent/data/chunk_utils.h>
|
||||||
#include <torrent/net/fd.h>
|
#include <torrent/net/fd.h>
|
||||||
|
#include <torrent/net/http_stack.h>
|
||||||
|
#include <torrent/runtime/memory_manager.h>
|
||||||
|
#include <torrent/runtime/runtime.h>
|
||||||
#include <torrent/utils/chrono.h>
|
#include <torrent/utils/chrono.h>
|
||||||
#include <torrent/utils/log.h>
|
#include <torrent/utils/log.h>
|
||||||
|
|
||||||
@@ -109,8 +112,9 @@ main(int argc, char** argv) {
|
|||||||
|
|
||||||
torrent::log_initialize();
|
torrent::log_initialize();
|
||||||
|
|
||||||
// TODO: Create a fake thread object for initializing other processes and enabling logging.
|
torrent::runtime::initialize_worker_process_and_main_thread([argc, argv]() {
|
||||||
torrent::initialize_main_thread();
|
parse_config_file(argc, argv, [](auto& path) { parse_config_file_comments("worker", path); });
|
||||||
|
});
|
||||||
|
|
||||||
// Block SIGCHLD until all threads are created, then unblock on main-thread, to avoid SIGCHLD
|
// Block SIGCHLD until all threads are created, then unblock on main-thread, to avoid SIGCHLD
|
||||||
// interrupting other threads.
|
// interrupting other threads.
|
||||||
@@ -122,9 +126,9 @@ main(int argc, char** argv) {
|
|||||||
SignalHandler::set_block(SIGCHLD);
|
SignalHandler::set_block(SIGCHLD);
|
||||||
|
|
||||||
// All signal handlers must restore errno if they return.
|
// All signal handlers must restore errno if they return.
|
||||||
SignalHandler::set_handler(SIGSEGV, std::bind(&do_panic, SIGSEGV));
|
SignalHandler::set_handler(SIGSEGV, []() { do_panic(SIGSEGV); });
|
||||||
SignalHandler::set_handler(SIGILL, std::bind(&do_panic, SIGILL));
|
SignalHandler::set_handler(SIGILL, []() { do_panic(SIGILL); });
|
||||||
SignalHandler::set_handler(SIGFPE, std::bind(&do_panic, SIGFPE));
|
SignalHandler::set_handler(SIGFPE, []() { do_panic(SIGFPE); });
|
||||||
|
|
||||||
// Limited list of commands with the following format:
|
// Limited list of commands with the following format:
|
||||||
//
|
//
|
||||||
@@ -133,24 +137,22 @@ main(int argc, char** argv) {
|
|||||||
// # do:log.open_file=system,/usr/rakshasa/system.log
|
// # do:log.open_file=system,/usr/rakshasa/system.log
|
||||||
// # do:log.add_output=system,system
|
// # do:log.add_output=system,system
|
||||||
//
|
//
|
||||||
parse_config_file(argc, argv, [](auto& path) {
|
// # do-worker:log.open_file=system,/usr/rakshasa/system-worker.log
|
||||||
if (path.empty())
|
// # do-worker:log.add_output=system,system
|
||||||
return;
|
//
|
||||||
|
parse_config_file(argc, argv, [](auto& path) { parse_config_file_comments("", path); });
|
||||||
parse_config_file_comments(path);
|
|
||||||
});
|
|
||||||
|
|
||||||
control = new Control;
|
control = new Control;
|
||||||
|
|
||||||
SignalHandler::set_handler(SIGINT, std::bind(&Control::receive_normal_shutdown, control));
|
SignalHandler::set_handler(SIGINT, []() { control->receive_normal_shutdown(); });
|
||||||
SignalHandler::set_handler(SIGHUP, std::bind(&Control::receive_normal_shutdown, control));
|
SignalHandler::set_handler(SIGHUP, []() { control->receive_normal_shutdown(); });
|
||||||
SignalHandler::set_handler(SIGTERM, std::bind(&Control::receive_quick_shutdown, control));
|
SignalHandler::set_handler(SIGTERM, []() { control->receive_quick_shutdown(); });
|
||||||
SignalHandler::set_handler(SIGWINCH, std::bind(&display::Manager::force_redraw, control->display()));
|
SignalHandler::set_handler(SIGWINCH, []() { control->display()->force_redraw(); });
|
||||||
|
|
||||||
SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus);
|
SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus);
|
||||||
|
|
||||||
torrent::log_add_group_output(torrent::LOG_NOTICE, "important");
|
torrent::log_add_group_output(torrent::LOG_NOTICE, "important");
|
||||||
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important");
|
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "important");
|
||||||
|
|
||||||
torrent::log_add_group_output(torrent::LOG_INFO, "complete");
|
torrent::log_add_group_output(torrent::LOG_INFO, "complete");
|
||||||
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "complete");
|
torrent::log_add_group_output(torrent::LOG_DHT_ERROR, "complete");
|
||||||
@@ -298,8 +300,6 @@ main(int argc, char** argv) {
|
|||||||
"schedule = low_diskspace,5,60,((close_low_diskspace,500M))\n"
|
"schedule = low_diskspace,5,60,((close_low_diskspace,500M))\n"
|
||||||
"schedule = prune_file_status,3600,86400,((system.file_status_cache.prune))\n"
|
"schedule = prune_file_status,3600,86400,((system.file_status_cache.prune))\n"
|
||||||
|
|
||||||
"protocol.encryption.set=allow_incoming,prefer_plaintext,enable_retry\n"
|
|
||||||
|
|
||||||
"ui.color.focus.set=reverse\n"
|
"ui.color.focus.set=reverse\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -343,8 +343,9 @@ main(int argc, char** argv) {
|
|||||||
CMD_REDIRECT("directory", "directory.default.set");
|
CMD_REDIRECT("directory", "directory.default.set");
|
||||||
CMD_REDIRECT("session", "session.path.set");
|
CMD_REDIRECT("session", "session.path.set");
|
||||||
|
|
||||||
CMD_REDIRECT("scgi_port", "network.scgi.open_port");
|
CMD_REDIRECT_NO_EXPORT("port_range", "network.listen.port.range.set");
|
||||||
CMD_REDIRECT("scgi_local", "network.scgi.open_local");
|
CMD_REDIRECT_NO_EXPORT("scgi_port", "network.scgi.open_port");
|
||||||
|
CMD_REDIRECT_NO_EXPORT("scgi_local", "network.scgi.open_local");
|
||||||
|
|
||||||
CMD_REDIRECT("to_gm_time", "convert.gm_time");
|
CMD_REDIRECT("to_gm_time", "convert.gm_time");
|
||||||
CMD_REDIRECT("to_gm_date", "convert.gm_date");
|
CMD_REDIRECT("to_gm_date", "convert.gm_date");
|
||||||
@@ -388,17 +389,14 @@ main(int argc, char** argv) {
|
|||||||
CMD_REDIRECT("schedule2", "schedule");
|
CMD_REDIRECT("schedule2", "schedule");
|
||||||
CMD_REDIRECT("schedule_remove2", "schedule.remove");
|
CMD_REDIRECT("schedule_remove2", "schedule.remove");
|
||||||
|
|
||||||
// TODO: Remove file.append when cleaning these up.
|
|
||||||
|
|
||||||
CMD_REDIRECT("bind", "network.bind_address.set");
|
CMD_REDIRECT("bind", "network.bind_address.set");
|
||||||
CMD_REDIRECT("ip", "network.local_address.set");
|
CMD_REDIRECT("ip", "network.local_address.set");
|
||||||
CMD_REDIRECT("port_range", "network.port_range.set");
|
|
||||||
|
|
||||||
// TODO: Check if dht is on by default.
|
// TODO: Check if dht is on by default.
|
||||||
CMD_REDIRECT("dht", "dht.mode.set");
|
CMD_REDIRECT("dht", "dht.mode.set");
|
||||||
|
|
||||||
CMD_REDIRECT("port_random", "network.port_random.set");
|
CMD_REDIRECT("port_random", "network.listen.port.random.set");
|
||||||
CMD_REDIRECT("proxy_address", "network.proxy_address.set");
|
CMD_REDIRECT("proxy_address", "network.proxy.http.set");
|
||||||
|
|
||||||
CMD_REDIRECT("key_layout", "keys.layout.set");
|
CMD_REDIRECT("key_layout", "keys.layout.set");
|
||||||
|
|
||||||
@@ -417,6 +415,20 @@ main(int argc, char** argv) {
|
|||||||
lt_log_print(torrent::LOG_WARN, "The 'throttle.ip' command is deprecated and does nothing.");
|
lt_log_print(torrent::LOG_WARN, "The 'throttle.ip' command is deprecated and does nothing.");
|
||||||
return torrent::Object();
|
return torrent::Object();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CMD_ANY("network.port_open", [](auto, auto) {
|
||||||
|
lt_log_print(torrent::LOG_WARN, "The 'network.port_open' command is deprecated and does nothing.");
|
||||||
|
return torrent::Object();
|
||||||
|
});
|
||||||
|
CMD_ANY("network.port_open.set", [](auto, auto) {
|
||||||
|
lt_log_print(torrent::LOG_WARN, "The 'network.port_open.set' command is deprecated and does nothing.");
|
||||||
|
return torrent::Object();
|
||||||
|
});
|
||||||
|
|
||||||
|
CMD_REDIRECT("network.port_random", "network.listen.port.random");
|
||||||
|
CMD_REDIRECT("network.port_random.set", "network.listen.port.random.set");
|
||||||
|
CMD_REDIRECT("network.port_range", "network.listen.port.range");
|
||||||
|
CMD_REDIRECT("network.port_range.set", "network.listen.port.range.set");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -444,10 +456,16 @@ main(int argc, char** argv) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
LT_LOG("seeded srandom and srand48 (seed:%u)", random_seed);
|
LT_LOG("seeded srandom and srand48 (seed:%u)", random_seed);
|
||||||
|
LT_LOG("max memory usage: %" PRIu64, torrent::runtime::memory_manager()->max_memory_usage());
|
||||||
|
|
||||||
control->initialize();
|
control->initialize();
|
||||||
control->ui()->load_input_history();
|
control->ui()->load_input_history();
|
||||||
|
|
||||||
|
torrent::net_thread::http_stack()->set_user_agent(USER_AGENT);
|
||||||
|
|
||||||
|
torrent::runtime::initialize_worker_process();
|
||||||
|
torrent::runtime::initialize_network();
|
||||||
|
|
||||||
// Load session torrents and perform scheduled tasks to ensure session torrents are loaded
|
// Load session torrents and perform scheduled tasks to ensure session torrents are loaded
|
||||||
// before arg torrents.
|
// before arg torrents.
|
||||||
control->dht_manager()->set_auto_if_untouched_and_has_session();
|
control->dht_manager()->set_auto_if_untouched_and_has_session();
|
||||||
|
|||||||
@@ -108,6 +108,6 @@ OptionParser::call_int_pair(slot_int_pair slot, const std::string& arg) {
|
|||||||
|
|
||||||
if (a < 0 || b < 0)
|
if (a < 0 || b < 0)
|
||||||
throw std::runtime_error("Invalid argument, \"" + arg + "\" should be positive numbers");
|
throw std::runtime_error("Invalid argument, \"" + arg + "\" should be positive numbers");
|
||||||
|
|
||||||
slot(a, b);
|
slot(a, b);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-16
@@ -55,12 +55,11 @@ struct rt_triple : private std::pair<T1, T2> {
|
|||||||
base_type(src.first, src.second), third(src.third) {}
|
base_type(src.first, src.second), third(src.third) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef rt_triple<int, void*, void*> target_type;
|
|
||||||
|
|
||||||
class command_base;
|
class command_base;
|
||||||
|
|
||||||
typedef const torrent::Object (*command_base_call_type)(command_base*, target_type, const torrent::Object&);
|
using target_type = rt_triple<int, void*, void*>;
|
||||||
typedef std::function<torrent::Object (target_type, const torrent::Object&)> base_function;
|
using base_function = std::function<torrent::Object (target_type, const torrent::Object&)>;
|
||||||
|
using command_base_call_type = const torrent::Object (command_base*, target_type, const torrent::Object&);
|
||||||
|
|
||||||
template <typename tmpl> struct command_base_is_valid {};
|
template <typename tmpl> struct command_base_is_valid {};
|
||||||
template <command_base_call_type tmpl_func> struct command_base_is_type {};
|
template <command_base_call_type tmpl_func> struct command_base_is_type {};
|
||||||
@@ -84,17 +83,18 @@ public:
|
|||||||
|
|
||||||
typedef const torrent::Object (*download_pair_slot) (command_base*, core::Download*, core::Download*, const torrent::Object&);
|
typedef const torrent::Object (*download_pair_slot) (command_base*, core::Download*, core::Download*, const torrent::Object&);
|
||||||
|
|
||||||
static const int target_generic = 0;
|
static constexpr int target_generic = 0;
|
||||||
static const int target_any = 1;
|
static constexpr int target_any = 1;
|
||||||
static const int target_download = 2;
|
static constexpr int target_download = 2;
|
||||||
static const int target_peer = 3;
|
static constexpr int target_peer = 3;
|
||||||
static const int target_tracker = 4;
|
static constexpr int target_tracker = 4;
|
||||||
static const int target_file = 5;
|
static constexpr int target_file = 5;
|
||||||
static const int target_file_itr = 6;
|
static constexpr int target_file_itr = 6;
|
||||||
|
static constexpr int target_download_pair = 7;
|
||||||
|
|
||||||
static const int target_download_pair = 7;
|
static constexpr unsigned int max_arguments = 10;
|
||||||
|
|
||||||
static const unsigned int max_arguments = 10;
|
static constexpr std::size_t optimal_alignment = std::max(alignof(std::max_align_t), alignof(base_function));
|
||||||
|
|
||||||
struct stack_type {
|
struct stack_type {
|
||||||
torrent::Object* begin() { return reinterpret_cast<torrent::Object*>(buffer); }
|
torrent::Object* begin() { return reinterpret_cast<torrent::Object*>(buffer); }
|
||||||
@@ -169,8 +169,7 @@ public:
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
void set_function(T s, [[maybe_unused]] int value = command_base_is_valid<T>::value) {
|
void set_function(T s, [[maybe_unused]] int value = command_base_is_valid<T>::value) {
|
||||||
static_assert(sizeof(T) <= sizeof(t_pod), "t_pod storage overflow");
|
static_assert(sizeof(T) <= sizeof(t_pod), "t_pod storage overflow");
|
||||||
static_assert(alignof(std::max_align_t) % alignof(T) == 0, "t_pod alignment insufficient for type");
|
static_assert(optimal_alignment >= alignof(T), "t_pod alignment insufficient for type");
|
||||||
static_assert(alignof(std::max_align_t) >= alignof(T), "t_pod structural capacity mismatch");
|
|
||||||
|
|
||||||
if (m_dest_helper)
|
if (m_dest_helper)
|
||||||
m_dest_helper(t_pod);
|
m_dest_helper(t_pod);
|
||||||
@@ -200,7 +199,7 @@ protected:
|
|||||||
using copy_fn_t = void (*)(void* dest, const void* src);
|
using copy_fn_t = void (*)(void* dest, const void* src);
|
||||||
using dest_fn_t = void (*)(void* ptr);
|
using dest_fn_t = void (*)(void* ptr);
|
||||||
|
|
||||||
alignas(std::max_align_t) char t_pod[sizeof(base_function)];
|
alignas(optimal_alignment) char t_pod[sizeof(base_function)];
|
||||||
|
|
||||||
copy_fn_t m_copy_helper;
|
copy_fn_t m_copy_helper;
|
||||||
dest_fn_t m_dest_helper;
|
dest_fn_t m_dest_helper;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ private:
|
|||||||
uint32_t m_interval{};
|
uint32_t m_interval{};
|
||||||
std::chrono::microseconds m_time_scheduled;
|
std::chrono::microseconds m_time_scheduled;
|
||||||
|
|
||||||
torrent::utils::SchedulerEntry m_task;
|
torrent::system::SchedulerEntry m_task;
|
||||||
|
|
||||||
// Flags for various things.
|
// Flags for various things.
|
||||||
};
|
};
|
||||||
|
|||||||
+46
-147
@@ -1,22 +1,22 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <cerrno>
|
#include "rpc/exec_file.h"
|
||||||
#include <cstring>
|
|
||||||
#include <fcntl.h>
|
// #include <cassert>
|
||||||
#include <spawn.h>
|
// #include <cerrno>
|
||||||
#include <string>
|
// #include <cstring>
|
||||||
|
// #include <fcntl.h>
|
||||||
|
// #include <spawn.h>
|
||||||
|
// #include <string>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/types.h>
|
// #include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/uio.h>
|
||||||
#include <torrent/net/fd.h>
|
// #include <torrent/net/fd.h>
|
||||||
#include <torrent/system/system.h>
|
|
||||||
#include <torrent/system/thread.h>
|
#include <torrent/system/thread.h>
|
||||||
|
#include <torrent/system/spawn_process.h>
|
||||||
|
#include <torrent/system/types.h>
|
||||||
|
|
||||||
#include "exec_file.h"
|
#include "rpc/parse.h"
|
||||||
#include "parse.h"
|
|
||||||
|
|
||||||
// Standard POSIX environment pointer
|
|
||||||
extern char** environ;
|
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
@@ -24,157 +24,56 @@ namespace rpc {
|
|||||||
|
|
||||||
int
|
int
|
||||||
ExecFile::execute(const char* file, char* const* argv, int flags) {
|
ExecFile::execute(const char* file, char* const* argv, int flags) {
|
||||||
// Write the executed command and its parameters to the log fd.
|
torrent::system::SpawnProcess spawn_process;
|
||||||
[[maybe_unused]] int result;
|
|
||||||
|
spawn_process.set_log_fd(m_log_fd);
|
||||||
|
spawn_process.set_background(flags & flag_background);
|
||||||
|
spawn_process.set_capture_output(flags & flag_capture);
|
||||||
|
|
||||||
if (m_log_fd != -1) {
|
if (m_log_fd != -1) {
|
||||||
for (char* const* itr = argv; *itr != NULL; itr++) {
|
std::vector<struct iovec> iovecs;
|
||||||
if (itr == argv)
|
iovecs.reserve(32);
|
||||||
result = write(m_log_fd, "\n---\n", sizeof("\n---\n"));
|
iovecs.push_back({const_cast<char*>("\n---\n"), 5});
|
||||||
else
|
|
||||||
result = write(m_log_fd, " ", 1);
|
|
||||||
|
|
||||||
result = write(m_log_fd, *itr, std::strlen(*itr));
|
for (auto* itr = argv; *itr != nullptr; itr++) {
|
||||||
|
if (itr != argv)
|
||||||
|
iovecs.push_back({const_cast<char*>(" "), 1});
|
||||||
|
|
||||||
|
iovecs.push_back({*itr, std::strlen(*itr)});
|
||||||
}
|
}
|
||||||
|
|
||||||
result = write(m_log_fd, "\n---\n", sizeof("\n---\n"));
|
iovecs.push_back({const_cast<char*>("\n---\n"), 5});
|
||||||
|
|
||||||
|
[[maybe_unused]] int result = ::writev(m_log_fd, iovecs.data(), iovecs.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
posix_spawn_file_actions_t actions{};
|
int spawn_status = spawn_process.execute(file, argv);
|
||||||
|
|
||||||
if (posix_spawn_file_actions_init(&actions) != 0)
|
|
||||||
throw torrent::internal_error("ExecFile::execute(...) posix_spawn_file_actions_init failed.");
|
|
||||||
|
|
||||||
posix_spawnattr_t attr;
|
|
||||||
posix_spawnattr_init(&attr);
|
|
||||||
|
|
||||||
// Try to avoid leaking open fds to the spawned process. Prefer POSIX_SPAWN_CLOEXEC_DEFAULT
|
|
||||||
// (macOS-only) or posix_spawn_file_actions_addclosefrom_np (glibc >= 2.34, FreeBSD >= 13.1).
|
|
||||||
//
|
|
||||||
// Other platforms like musl libc, OpenBSD and NetBSD must rely on explicit O_CLOEXEC.
|
|
||||||
|
|
||||||
// Handle standard input redirection (/dev/null), posix_spawn_file_actions_addopen handles opening
|
|
||||||
// and dup2 natively
|
|
||||||
if (posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDWR, 0) != 0) {
|
|
||||||
// Fallback if open fails inside action setup
|
|
||||||
posix_spawn_file_actions_addclose(&actions, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int pipe_0 = -1;
|
|
||||||
int pipe_1 = -1;
|
|
||||||
|
|
||||||
// Handle standard output redirection
|
|
||||||
if (flags & flag_capture) {
|
|
||||||
torrent::fd_open_pipe(pipe_0, pipe_1);
|
|
||||||
|
|
||||||
posix_spawn_file_actions_adddup2(&actions, pipe_1, 1);
|
|
||||||
|
|
||||||
// Ensure the write end of the pipe is closed in the child after duplicating.
|
|
||||||
posix_spawn_file_actions_addclose(&actions, pipe_0);
|
|
||||||
posix_spawn_file_actions_addclose(&actions, pipe_1);
|
|
||||||
|
|
||||||
} else if (m_log_fd != -1) {
|
|
||||||
posix_spawn_file_actions_adddup2(&actions, m_log_fd, 1);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
posix_spawn_file_actions_addopen(&actions, 1, "/dev/null", O_RDWR, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_log_fd != -1) {
|
|
||||||
posix_spawn_file_actions_adddup2(&actions, m_log_fd, 2);
|
|
||||||
} else {
|
|
||||||
posix_spawn_file_actions_addopen(&actions, 2, "/dev/null", O_RDWR, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
short spawn_flags = 0;
|
|
||||||
|
|
||||||
#if defined(POSIX_SPAWN_CLOEXEC_DEFAULT)
|
|
||||||
spawn_flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
|
|
||||||
#elif defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP)
|
|
||||||
posix_spawn_file_actions_addclosefrom_np(&actions, 3);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (flags & flag_background) {
|
|
||||||
#ifdef POSIX_SPAWN_SETSID
|
|
||||||
spawn_flags |= POSIX_SPAWN_SETSID;
|
|
||||||
#else
|
|
||||||
spawn_flags |= POSIX_SPAWN_SETPGROUP;
|
|
||||||
posix_spawnattr_setpgroup(&attr, 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
posix_spawnattr_setflags(&attr, spawn_flags);
|
|
||||||
|
|
||||||
pid_t child_pid{};
|
|
||||||
int spawn_status = posix_spawnp(&child_pid, file, &actions, &attr, argv, environ);
|
|
||||||
|
|
||||||
posix_spawn_file_actions_destroy(&actions);
|
|
||||||
posix_spawnattr_destroy(&attr);
|
|
||||||
|
|
||||||
if (spawn_status != 0) {
|
if (spawn_status != 0) {
|
||||||
if (pipe_0 != -1)
|
if (m_log_fd != -1) {
|
||||||
torrent::fd_close(pipe_0);
|
auto prefix = "\n--- posix_spawn failed: ";
|
||||||
|
auto errno_str = torrent::system::errno_enum_str(spawn_status) + " ---\n";
|
||||||
|
|
||||||
if (pipe_1 != -1)
|
struct iovec iovecs[2] = {
|
||||||
torrent::fd_close(pipe_1);
|
{const_cast<char*>(prefix), std::strlen(prefix)},
|
||||||
|
{const_cast<char*>(errno_str.c_str()), errno_str.size()},
|
||||||
|
};
|
||||||
|
|
||||||
|
[[maybe_unused]] int result = ::writev(m_log_fd, iovecs, 2);
|
||||||
|
}
|
||||||
|
|
||||||
throw torrent::input_error("ExecFile::execute() posix_spawn failed: " + torrent::system::errno_enum_str(spawn_status));
|
throw torrent::input_error("ExecFile::execute() posix_spawn failed: " + torrent::system::errno_enum_str(spawn_status));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & flag_capture) {
|
|
||||||
m_capture = std::string();
|
|
||||||
torrent::fd_close(pipe_1);
|
|
||||||
|
|
||||||
char buffer[4096];
|
|
||||||
ssize_t length;
|
|
||||||
|
|
||||||
do {
|
|
||||||
length = read(pipe_0, buffer, sizeof(buffer));
|
|
||||||
|
|
||||||
if (length > 0)
|
|
||||||
m_capture += std::string(buffer, length);
|
|
||||||
|
|
||||||
} while (length > 0);
|
|
||||||
|
|
||||||
torrent::fd_close(pipe_0);
|
|
||||||
|
|
||||||
if (m_log_fd != -1) {
|
|
||||||
result = write(m_log_fd, "Captured output:\n", sizeof("Captured output:\n"));
|
|
||||||
result = write(m_log_fd, m_capture.data(), m_capture.length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & flag_background) {
|
if (flags & flag_background) {
|
||||||
if (m_log_fd != -1)
|
m_waitpid_queue.close_pid(spawn_process.child_pid());
|
||||||
result = write(m_log_fd, "\n--- Running in Background ---\n", sizeof("\n--- Running in Background ---\n"));
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int status;
|
if (flags & flag_capture)
|
||||||
|
m_capture = spawn_process.capture_child_output();
|
||||||
|
|
||||||
while (::waitpid(child_pid, &status, 0) == -1) {
|
return spawn_process.wait_for_child();
|
||||||
switch (errno) {
|
|
||||||
case EINTR:
|
|
||||||
continue;
|
|
||||||
case ECHILD:
|
|
||||||
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with ECHILD, child process not found.");
|
|
||||||
case EINVAL:
|
|
||||||
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with EINVAL.");
|
|
||||||
default:
|
|
||||||
throw torrent::internal_error("ExecFile::execute(...) waitpid failed with unexpected error: " + std::string(std::strerror(errno)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check return value?
|
|
||||||
if (m_log_fd != -1) {
|
|
||||||
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
|
|
||||||
result = write(m_log_fd, "\n--- Success ---\n", sizeof("\n--- Success ---\n"));
|
|
||||||
else
|
|
||||||
result = write(m_log_fd, "\n--- Error ---\n", sizeof("\n--- Error ---\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent::Object
|
torrent::Object
|
||||||
@@ -234,4 +133,4 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
|
|||||||
return torrent::Object((int64_t)status);
|
return torrent::Object((int64_t)status);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace rpc
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include <torrent/object.h>
|
#include <torrent/object.h>
|
||||||
|
|
||||||
|
#include "utils/waitpid_queue.h"
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
class ExecFile {
|
class ExecFile {
|
||||||
@@ -24,6 +26,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
int m_log_fd{-1};
|
int m_log_fd{-1};
|
||||||
std::string m_capture;
|
std::string m_capture;
|
||||||
|
|
||||||
|
utils::WaitpidQueue m_waitpid_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-37
@@ -113,25 +113,20 @@ bool
|
|||||||
RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
|
RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case RPCType::XML:
|
case RPCType::XML:
|
||||||
// TODO: 'network.rpc.use_xmlrpc' should be a bool in RpcManager, not a command variable.
|
if (!m_xmlrpc.is_valid() || !m_use_xmlrpc) {
|
||||||
if (m_xmlrpc.is_valid() && rpc::call_command_value("network.rpc.use_xmlrpc")) {
|
|
||||||
return m_xmlrpc.process(in_buffer, length, callback);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></value></fault></methodResponse>";
|
const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></value></fault></methodResponse>";
|
||||||
return callback(response.c_str(), response.size());
|
return callback(response.c_str(), response.size());
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
return m_xmlrpc.process(in_buffer, length, callback);
|
||||||
|
|
||||||
case RPCType::JSON:
|
case RPCType::JSON:
|
||||||
if (rpc::call_command_value("network.rpc.use_jsonrpc")) {
|
if (!m_use_jsonrpc) {
|
||||||
return m_jsonrpc.process(in_buffer, length, callback);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}";
|
const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}";
|
||||||
return callback(response.c_str(), response.size());
|
return callback(response.c_str(), response.size());
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
return m_jsonrpc.process(in_buffer, length, callback);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw torrent::input_error("invalid parameters: unknown RPC type");
|
throw torrent::input_error("invalid parameters: unknown RPC type");
|
||||||
@@ -172,32 +167,6 @@ RpcManager::cleanup() {
|
|||||||
m_jsonrpc.cleanup();
|
m_jsonrpc.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
RpcManager::is_type_enabled(RPCType type) const {
|
|
||||||
switch (type) {
|
|
||||||
case RPCType::XML:
|
|
||||||
return m_is_xmlrpc_enabled;
|
|
||||||
case RPCType::JSON:
|
|
||||||
return m_is_jsonrpc_enabled;
|
|
||||||
default:
|
|
||||||
throw torrent::input_error("invalid parameters: unknown RPC type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
RpcManager::set_type_enabled(RPCType type, bool enabled) {
|
|
||||||
switch (type) {
|
|
||||||
case RPCType::XML:
|
|
||||||
m_is_xmlrpc_enabled = enabled;
|
|
||||||
break;
|
|
||||||
case RPCType::JSON:
|
|
||||||
m_is_jsonrpc_enabled = enabled;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw torrent::input_error("invalid parameters: unknown RPC type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
|
RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
|
||||||
m_xmlrpc.insert_command(name, parm, doc);
|
m_xmlrpc.insert_command(name, parm, doc);
|
||||||
|
|||||||
+13
-4
@@ -67,8 +67,11 @@ public:
|
|||||||
int dialect() { return m_xmlrpc.dialect(); }
|
int dialect() { return m_xmlrpc.dialect(); }
|
||||||
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
|
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
|
||||||
|
|
||||||
bool is_type_enabled(RPCType type) const;
|
bool use_xmlrpc() const;
|
||||||
void set_type_enabled(RPCType type, bool enabled);
|
void set_use_xmlrpc(bool v);
|
||||||
|
|
||||||
|
bool use_jsonrpc() const;
|
||||||
|
void set_use_jsonrpc(bool v);
|
||||||
|
|
||||||
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);
|
bool process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
|
||||||
@@ -102,8 +105,9 @@ private:
|
|||||||
JsonRpc m_jsonrpc;
|
JsonRpc m_jsonrpc;
|
||||||
|
|
||||||
bool m_handlers_initialized{};
|
bool m_handlers_initialized{};
|
||||||
bool m_is_jsonrpc_enabled{true};
|
|
||||||
bool m_is_xmlrpc_enabled{true};
|
bool m_use_jsonrpc{true};
|
||||||
|
bool m_use_xmlrpc{true};
|
||||||
|
|
||||||
std::atomic<bool> m_scgi_allow_compression{true};
|
std::atomic<bool> m_scgi_allow_compression{true};
|
||||||
std::atomic<unsigned int> m_scgi_min_compress_size{1000};
|
std::atomic<unsigned int> m_scgi_min_compress_size{1000};
|
||||||
@@ -116,6 +120,11 @@ private:
|
|||||||
|
|
||||||
extern RpcManager rpc;
|
extern RpcManager rpc;
|
||||||
|
|
||||||
|
inline bool RpcManager::use_xmlrpc() const { return m_use_xmlrpc; }
|
||||||
|
inline void RpcManager::set_use_xmlrpc(bool v) { m_use_xmlrpc = v; }
|
||||||
|
inline bool RpcManager::use_jsonrpc() const { return m_use_jsonrpc; }
|
||||||
|
inline void RpcManager::set_use_jsonrpc(bool v) { m_use_jsonrpc = v; }
|
||||||
|
|
||||||
} // namespace rpc
|
} // namespace rpc
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+1
-3
@@ -109,9 +109,7 @@ void
|
|||||||
SCgi::activate() {
|
SCgi::activate() {
|
||||||
assert(torrent::this_thread::thread() == scgi_thread::thread());
|
assert(torrent::this_thread::thread() == scgi_thread::thread());
|
||||||
|
|
||||||
torrent::this_thread::poll()->open(this);
|
torrent::this_thread::poll()->open_and_insert_read(this);
|
||||||
torrent::this_thread::poll()->insert_read(this);
|
|
||||||
torrent::this_thread::poll()->insert_error(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should close the fd to avoid reuse.
|
// TODO: This should close the fd to avoid reuse.
|
||||||
|
|||||||
+2
-2
@@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <torrent/event.h>
|
#include <torrent/system/event.h>
|
||||||
|
|
||||||
#include "rpc/scgi_task.h"
|
#include "rpc/scgi_task.h"
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
class SCgi : public torrent::Event {
|
class SCgi : public torrent::system::Event {
|
||||||
public:
|
public:
|
||||||
static const int max_tasks = 100;
|
static const int max_tasks = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace rpc {
|
|||||||
SCgiTask::SCgiTask()
|
SCgiTask::SCgiTask()
|
||||||
: m_callback_id(torrent::system::make_callback_id()) {
|
: m_callback_id(torrent::system::make_callback_id()) {
|
||||||
|
|
||||||
set_file_descriptor(-1);
|
reset_file_descriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -47,9 +47,7 @@ SCgiTask::open(SCgi* parent, int fd) {
|
|||||||
// m_trusted=false into the next reuse, given that the
|
// m_trusted=false into the next reuse, given that the
|
||||||
// UNTRUSTED_CONNECTION=0 parse branch is a no-op.
|
// UNTRUSTED_CONNECTION=0 parse branch is a no-op.
|
||||||
|
|
||||||
torrent::this_thread::poll()->open(this);
|
torrent::this_thread::poll()->open_and_insert_read(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);
|
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||||
|
|
||||||
@@ -65,7 +63,7 @@ SCgiTask::cancel_open() {
|
|||||||
torrent::this_thread::poll()->remove_and_close(this);
|
torrent::this_thread::poll()->remove_and_close(this);
|
||||||
|
|
||||||
torrent::fd_close(file_descriptor());
|
torrent::fd_close(file_descriptor());
|
||||||
set_file_descriptor(-1);
|
reset_file_descriptor();
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -79,7 +77,7 @@ SCgiTask::close() {
|
|||||||
torrent::this_thread::poll()->remove_and_close(this);
|
torrent::this_thread::poll()->remove_and_close(this);
|
||||||
|
|
||||||
torrent::fd_close(file_descriptor());
|
torrent::fd_close(file_descriptor());
|
||||||
set_file_descriptor(-1);
|
reset_file_descriptor();
|
||||||
});
|
});
|
||||||
|
|
||||||
// The callbacks are guaranteed to be finished/canceled at this point.
|
// The callbacks are guaranteed to be finished/canceled at this point.
|
||||||
@@ -98,7 +96,7 @@ SCgiTask::event_read() {
|
|||||||
if (read_length <= 0)
|
if (read_length <= 0)
|
||||||
throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read.");
|
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);
|
int bytes = ::recv(file_descriptor(), m_buffer.data() + m_position, read_length, 0);
|
||||||
|
|
||||||
if (bytes <= 0) {
|
if (bytes <= 0) {
|
||||||
if (bytes == 0 || !(errno == EAGAIN || errno == EINTR))
|
if (bytes == 0 || !(errno == EAGAIN || errno == EINTR))
|
||||||
@@ -181,7 +179,7 @@ event_read_failed:
|
|||||||
|
|
||||||
void
|
void
|
||||||
SCgiTask::event_write() {
|
SCgiTask::event_write() {
|
||||||
int bytes = ::send(m_fileDesc, m_buffer.data() + m_position, m_buffer.size() - m_position, 0);
|
int bytes = ::send(file_descriptor(), m_buffer.data() + m_position, m_buffer.size() - m_position, 0);
|
||||||
|
|
||||||
if (bytes == -1) {
|
if (bytes == -1) {
|
||||||
if (!(errno == EAGAIN || errno == EINTR))
|
if (!(errno == EAGAIN || errno == EINTR))
|
||||||
|
|||||||
+4
-4
@@ -4,13 +4,13 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <torrent/event.h>
|
#include <torrent/system/event.h>
|
||||||
|
|
||||||
namespace rpc {
|
namespace rpc {
|
||||||
|
|
||||||
class SCgi;
|
class SCgi;
|
||||||
|
|
||||||
class SCgiTask : public torrent::Event {
|
class SCgiTask : public torrent::system::Event {
|
||||||
public:
|
public:
|
||||||
static constexpr int default_buffer_size = 8191;
|
static constexpr int default_buffer_size = 8191;
|
||||||
static constexpr int max_header_size = 2000;
|
static constexpr int max_header_size = 2000;
|
||||||
@@ -22,8 +22,8 @@ public:
|
|||||||
|
|
||||||
const char* type_name() const override { return "scgi-task"; }
|
const char* type_name() const override { return "scgi-task"; }
|
||||||
|
|
||||||
bool is_open() const { return m_fileDesc != -1; }
|
bool is_open() const { return file_descriptor() != -1; }
|
||||||
bool is_available() const { return m_fileDesc == -1; }
|
bool is_available() const { return file_descriptor() == -1; }
|
||||||
|
|
||||||
void open(SCgi* parent, int fd);
|
void open(SCgi* parent, int fd);
|
||||||
void cancel_open();
|
void cancel_open();
|
||||||
|
|||||||
@@ -451,6 +451,11 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
|
|||||||
if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR)
|
if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR)
|
||||||
throw torrent::internal_error("Internal error in XMLRPC.");
|
throw torrent::internal_error("Internal error in XMLRPC.");
|
||||||
|
|
||||||
|
if (memblock == nullptr) {
|
||||||
|
xmlrpc_env_clean(&local_env);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
|
bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
|
||||||
xmlrpc_mem_block_size(memblock));
|
xmlrpc_mem_block_size(memblock));
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -38,7 +38,7 @@ parse_main_options(int argc, char** argv) {
|
|||||||
optionParser.insert_option('b', [](auto& arg) { rpc::call_command_set_string("network.bind_address.set", arg); });
|
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('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('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('p', [](auto& arg) { rpc::call_command_set_string("network.listen.port.range.set", arg); });
|
||||||
optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", 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('O', [](auto& arg) { rpc::parse_command_single_std(arg); });
|
||||||
@@ -108,18 +108,22 @@ config_comment_log(const std::string& command, const std::string& raw_args) {
|
|||||||
throw torrent::input_error("Unknown log command: " + command);
|
throw torrent::input_error("Unknown log command: " + command);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call special commands in the format "# do:command=args" in the config file.
|
// Call special commands in the format "# do:command=args" or "# do-<category>:command=args" in the config file.
|
||||||
void
|
void
|
||||||
parse_config_file_comments(const std::string& path) {
|
parse_config_file_comments(const std::string& category, const std::string& path) {
|
||||||
|
if (path.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
std::fstream file(path, std::ios::in);
|
std::fstream file(path, std::ios::in);
|
||||||
|
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
std::string prefix = category.empty() ? "# do:" : "# do-" + category + ":";
|
||||||
|
|
||||||
while (std::getline(file, line)) {
|
while (std::getline(file, line)) {
|
||||||
if (line.size() <= 5 || line.compare(0, 5, "# do:") != 0)
|
if (line.size() <= prefix.size() || line.compare(0, prefix.size(), prefix) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto equal_pos = line.find('=');
|
auto equal_pos = line.find('=');
|
||||||
@@ -127,8 +131,8 @@ parse_config_file_comments(const std::string& path) {
|
|||||||
if (equal_pos == std::string::npos)
|
if (equal_pos == std::string::npos)
|
||||||
throw torrent::input_error("Invalid command in config file comment: " + line);
|
throw torrent::input_error("Invalid command in config file comment: " + line);
|
||||||
|
|
||||||
std::string command = line.substr(5, equal_pos - 5);
|
auto command = line.substr(prefix.size(), equal_pos - prefix.size());
|
||||||
std::string args = line.substr(equal_pos + 1);
|
auto args = line.substr(equal_pos + 1);
|
||||||
|
|
||||||
if (command.empty())
|
if (command.empty())
|
||||||
throw torrent::input_error("Invalid command in config file comment: " + line);
|
throw torrent::input_error("Invalid command in config file comment: " + line);
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
int parse_main_options(int argc, char** argv);
|
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(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
|
||||||
void parse_config_file_comments(const std::string& path);
|
void parse_config_file_comments(const std::string& category, const std::string& path);
|
||||||
|
|
||||||
void load_session_torrents(const std::string& path);
|
void load_session_torrents(const std::string& path);
|
||||||
void load_arg_torrents(char** first, char** last);
|
void load_arg_torrents(char** first, char** last);
|
||||||
|
|||||||
+4
-7
@@ -3,7 +3,6 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include <torrent/exceptions.h>
|
#include <torrent/exceptions.h>
|
||||||
#include <torrent/chunk_manager.h>
|
|
||||||
#include <torrent/throttle.h>
|
#include <torrent/throttle.h>
|
||||||
#include <torrent/torrent.h>
|
#include <torrent/torrent.h>
|
||||||
#include <torrent/data/file_list.h>
|
#include <torrent/data/file_list.h>
|
||||||
@@ -31,10 +30,10 @@
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
Download::Download(core::Download* d) :
|
Download::Download(core::Download* d)
|
||||||
m_download(d) {
|
: m_download(d) {
|
||||||
|
|
||||||
m_windowDownloadStatus = new WDownloadStatus(d);
|
m_windowDownloadStatus = std::make_unique<WDownloadStatus>(d);
|
||||||
m_windowDownloadStatus->set_bottom(true);
|
m_windowDownloadStatus->set_bottom(true);
|
||||||
|
|
||||||
m_uiArray[DISPLAY_MENU] = create_menu();
|
m_uiArray[DISPLAY_MENU] = create_menu();
|
||||||
@@ -60,8 +59,6 @@ Download::~Download() {
|
|||||||
assert(!is_active() && "ui::Download::~Download() called on an active object.");
|
assert(!is_active() && "ui::Download::~Download() called on an active object.");
|
||||||
|
|
||||||
std::for_each(m_uiArray, m_uiArray + DISPLAY_MAX_SIZE, [](ElementBase* eb) { delete eb; });
|
std::for_each(m_uiArray, m_uiArray + DISPLAY_MAX_SIZE, [](ElementBase* eb) { delete eb; });
|
||||||
|
|
||||||
delete m_windowDownloadStatus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ElementBase*
|
inline ElementBase*
|
||||||
@@ -171,7 +168,7 @@ Download::activate(display::Frame* frame, [[maybe_unused]] bool focus) {
|
|||||||
m_frame = frame;
|
m_frame = frame;
|
||||||
m_frame->initialize_row(2);
|
m_frame->initialize_row(2);
|
||||||
|
|
||||||
m_frame->frame(1)->initialize_window(m_windowDownloadStatus);
|
m_frame->frame(1)->initialize_window(m_windowDownloadStatus.get());
|
||||||
m_windowDownloadStatus->set_active(true);
|
m_windowDownloadStatus->set_active(true);
|
||||||
|
|
||||||
activate_display_menu(DISPLAY_PEER_LIST);
|
activate_display_menu(DISPLAY_PEER_LIST);
|
||||||
|
|||||||
+1
-35
@@ -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_DOWNLOAD_H
|
#ifndef RTORRENT_UI_DOWNLOAD_H
|
||||||
#define RTORRENT_UI_DOWNLOAD_H
|
#define RTORRENT_UI_DOWNLOAD_H
|
||||||
|
|
||||||
@@ -113,7 +79,7 @@ private:
|
|||||||
|
|
||||||
bool m_focusDisplay{};
|
bool m_focusDisplay{};
|
||||||
|
|
||||||
WDownloadStatus* m_windowDownloadStatus;
|
std::unique_ptr<WDownloadStatus> m_windowDownloadStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ namespace ui {
|
|||||||
|
|
||||||
class ElementLogComplete : public ElementBase {
|
class ElementLogComplete : public ElementBase {
|
||||||
public:
|
public:
|
||||||
typedef display::WindowLogComplete WLogComplete;
|
using WLogComplete = display::WindowLogComplete;
|
||||||
|
|
||||||
ElementLogComplete(torrent::log_buffer* l);
|
ElementLogComplete(torrent::log_buffer* l);
|
||||||
~ElementLogComplete() override;
|
~ElementLogComplete() override;
|
||||||
|
|
||||||
void activate(display::Frame* frame, bool focus = true);
|
void activate(display::Frame* frame, bool focus = true) override;
|
||||||
void disable();
|
void disable() override;
|
||||||
|
|
||||||
display::Window* window();
|
display::Window* window();
|
||||||
|
|
||||||
@@ -32,7 +32,9 @@ private:
|
|||||||
|
|
||||||
torrent::log_buffer* m_log;
|
torrent::log_buffer* m_log;
|
||||||
|
|
||||||
align_cacheline std::atomic<bool> m_log_updating{};
|
align_cacheline
|
||||||
|
|
||||||
|
std::atomic<bool> m_log_updating{};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <torrent/throttle.h>
|
#include <torrent/throttle.h>
|
||||||
#include <torrent/torrent.h>
|
#include <torrent/torrent.h>
|
||||||
@@ -354,8 +355,9 @@ Root::reset_input_history_attributes(ui::DownloadList::Input type) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
Root::set_input_history_size(int size) {
|
Root::set_input_history_size(int size) {
|
||||||
if (size < 1)
|
if (size < 1 || size > max_input_history_size)
|
||||||
throw torrent::input_error("Invalid input history size.");
|
throw torrent::input_error("Input history size must be between 1 and " +
|
||||||
|
std::to_string(max_input_history_size) + ".");
|
||||||
|
|
||||||
for (auto& [entry, category] : m_input_history) {
|
for (auto& [entry, category] : m_input_history) {
|
||||||
// Reserve the latest input history entries if new size is smaller than original.
|
// Reserve the latest input history entries if new size is smaller than original.
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ public:
|
|||||||
typedef std::vector<std::string> InputHistoryCategory;
|
typedef std::vector<std::string> InputHistoryCategory;
|
||||||
typedef std::map<int, InputHistoryCategory> InputHistory;
|
typedef std::map<int, InputHistoryCategory> InputHistory;
|
||||||
|
|
||||||
|
static constexpr int max_input_history_size = 4096;
|
||||||
|
|
||||||
Root();
|
Root();
|
||||||
|
|
||||||
void init(Control* c);
|
void init(Control* c);
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "utils/waitpid_queue.h"
|
||||||
|
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <torrent/exceptions.h>
|
||||||
|
|
||||||
|
namespace utils {
|
||||||
|
|
||||||
|
WaitpidQueue::WaitpidQueue() {
|
||||||
|
m_worker = std::async(std::launch::async, [this]() {
|
||||||
|
auto wait_time = 50ms;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!m_queue.empty()) {
|
||||||
|
auto start_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
while (std::chrono::steady_clock::now() - start_time < wait_time) {
|
||||||
|
if (m_should_shutdown.load(std::memory_order_acquire))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(50ms);
|
||||||
|
|
||||||
|
if (m_wakeup_worker.load(std::memory_order_acquire))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
m_wakeup_worker.wait(false, std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a small delay to allow new processes to finish if they're quickly spawned and
|
||||||
|
// terminated.
|
||||||
|
std::this_thread::sleep_for(50ms);
|
||||||
|
|
||||||
|
std::set<pid_t> queue;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
|
|
||||||
|
if (m_should_shutdown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_queue.empty())
|
||||||
|
throw torrent::internal_error("WaitpidQueue worker thread woke up but queue is empty.");
|
||||||
|
|
||||||
|
queue = m_queue;
|
||||||
|
|
||||||
|
m_wakeup_worker.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_time = std::min(10 * 1000ms, wait_time * 2);
|
||||||
|
|
||||||
|
for (auto pid : queue) {
|
||||||
|
if (::waitpid(pid, nullptr, WNOHANG) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
|
|
||||||
|
if (m_queue.erase(pid) != 1)
|
||||||
|
throw torrent::internal_error("WaitpidQueue worker thread could not find pid in queue.");
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_time = std::max(50ms, wait_time / 2);
|
||||||
|
|
||||||
|
m_remaining.fetch_sub(1, std::memory_order_release);
|
||||||
|
m_remaining.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't wait for the worker thread to finish as waitpid isn't needed to be called on shutdown.
|
||||||
|
WaitpidQueue::~WaitpidQueue() {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
|
m_should_shutdown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wakeup_worker.store(true, std::memory_order_release);
|
||||||
|
m_wakeup_worker.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
WaitpidQueue::close_pid(pid_t pid) {
|
||||||
|
if (pid < 0)
|
||||||
|
throw torrent::internal_error("WaitpidQueue::close_pid() called with invalid pid.");
|
||||||
|
|
||||||
|
m_remaining.fetch_add(1, std::memory_order_acquire);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
|
m_queue.insert(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wakeup_worker.store(true, std::memory_order_release);
|
||||||
|
m_wakeup_worker.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
WaitpidQueue::wait_for(uint32_t max_remaining) {
|
||||||
|
while (m_remaining.load(std::memory_order_acquire) > max_remaining)
|
||||||
|
m_remaining.wait(max_remaining, std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace torrent::utils
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef RTORRENT_UTILS_WAITPID_QUEUE_H
|
||||||
|
#define RTORRENT_UTILS_WAITPID_QUEUE_H
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
#include <set>
|
||||||
|
#include <torrent/system/common.h>
|
||||||
|
|
||||||
|
namespace utils {
|
||||||
|
|
||||||
|
class WaitpidQueue {
|
||||||
|
public:
|
||||||
|
WaitpidQueue();
|
||||||
|
~WaitpidQueue();
|
||||||
|
|
||||||
|
uint32_t size() const;
|
||||||
|
|
||||||
|
void close_pid(int pid);
|
||||||
|
|
||||||
|
void wait_for(uint32_t max_remaining);
|
||||||
|
|
||||||
|
// TODO: Add a signal handler to tell the worker thread to wake up. Use a counter to batch wakeups.
|
||||||
|
|
||||||
|
private:
|
||||||
|
WaitpidQueue(const WaitpidQueue&) = delete;
|
||||||
|
WaitpidQueue& operator=(const WaitpidQueue&) = delete;
|
||||||
|
|
||||||
|
std::future<void> m_worker;
|
||||||
|
|
||||||
|
align_cacheline
|
||||||
|
|
||||||
|
std::mutex m_mutex;
|
||||||
|
std::set<pid_t> m_queue;
|
||||||
|
|
||||||
|
align_cacheline
|
||||||
|
|
||||||
|
std::atomic<bool> m_wakeup_worker{};
|
||||||
|
std::atomic<bool> m_should_shutdown{};
|
||||||
|
|
||||||
|
std::atomic<uint32_t> m_remaining{};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline uint32_t WaitpidQueue::size() const { return m_remaining.load(std::memory_order_acquire); }
|
||||||
|
|
||||||
|
} // namespace utils
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <torrent/utils/scheduler.h>
|
#include <torrent/system/scheduler.h>
|
||||||
|
|
||||||
namespace utils {
|
namespace utils {
|
||||||
|
|
||||||
@@ -49,10 +49,12 @@ private:
|
|||||||
void update_status(Entry* entry);
|
void update_status(Entry* entry);
|
||||||
void schedule();
|
void schedule();
|
||||||
|
|
||||||
std::map<std::string, Entry> m_entries;
|
std::map<std::string, Entry> m_entries;
|
||||||
std::vector<Entry*> m_entry_queue;
|
std::vector<Entry*> m_entry_queue;
|
||||||
torrent::utils::SchedulerEntry m_task_process;
|
|
||||||
bool m_active{true};
|
bool m_active{true};
|
||||||
|
|
||||||
|
torrent::system::SchedulerEntry m_task_process;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
|
|||||||
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
|
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
|
||||||
src/test_command_dynamic.cc \
|
src/test_command_dynamic.cc \
|
||||||
src/test_command_dynamic.h \
|
src/test_command_dynamic.h \
|
||||||
|
src/test_command_local.cc \
|
||||||
|
src/test_command_local.h \
|
||||||
|
src/test_command_path.cc \
|
||||||
|
src/test_command_path.h \
|
||||||
|
src/test_command_string.cc \
|
||||||
|
src/test_command_string.h \
|
||||||
src/test_watch_ready_queue.cc \
|
src/test_watch_ready_queue.cc \
|
||||||
src/test_watch_ready_queue.h
|
src/test_watch_ready_queue.h
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
#define LIBTORRENT_HELPERS_MOCK_COMPARE_H
|
#define LIBTORRENT_HELPERS_MOCK_COMPARE_H
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <torrent/event.h>
|
|
||||||
#include <torrent/net/socket_address.h>
|
#include <torrent/net/socket_address.h>
|
||||||
|
#include <torrent/system/event.h>
|
||||||
|
|
||||||
// Compare arguments to mock functions with what is expected. The lhs
|
// Compare arguments to mock functions with what is expected. The lhs
|
||||||
// are the expected arguments, rhs are the ones called with.
|
// are the expected arguments, rhs are the ones called with.
|
||||||
@@ -13,13 +14,13 @@ template <typename Arg>
|
|||||||
inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; }
|
inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; }
|
||||||
|
|
||||||
template <int I, typename A, typename... Args>
|
template <int I, typename A, typename... Args>
|
||||||
typename std::enable_if<I == 1, int>::type
|
std::enable_if_t<I == 1, int>
|
||||||
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
|
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
|
||||||
return mock_compare_arg(std::get<I>(lhs), std::get<I - 1>(rhs)) ? 0 : 1;
|
return mock_compare_arg(std::get<I>(lhs), std::get<I - 1>(rhs)) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int I, typename A, typename... Args>
|
template <int I, typename A, typename... Args>
|
||||||
typename std::enable_if<1 < I, int>::type
|
std::enable_if_t<1 < I, int>
|
||||||
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
|
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
|
||||||
auto res = mock_compare_tuple<I - 1>(lhs, rhs);
|
auto res = mock_compare_tuple<I - 1>(lhs, rhs);
|
||||||
|
|
||||||
@@ -70,24 +71,37 @@ void mock_compare_add(T* v) {
|
|||||||
// Specialize:
|
// Specialize:
|
||||||
//
|
//
|
||||||
|
|
||||||
|
constexpr int mock_compare_gt_two_int = -0xFD30;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool mock_compare_arg<int>(int lhs, int rhs) {
|
||||||
|
if (lhs == mock_compare_gt_two_int)
|
||||||
|
return rhs > 2;
|
||||||
|
if (rhs == mock_compare_gt_two_int)
|
||||||
|
return lhs > 2;
|
||||||
|
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool mock_compare_arg<sockaddr*>(sockaddr* lhs, sockaddr* rhs) {
|
inline bool mock_compare_arg<sockaddr*>(sockaddr* lhs, sockaddr* rhs) {
|
||||||
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
|
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool mock_compare_arg<const sockaddr*>(const sockaddr* lhs, const sockaddr* rhs) {
|
inline bool mock_compare_arg<const sockaddr*>(const sockaddr* lhs, const sockaddr* rhs) {
|
||||||
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
|
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline bool mock_compare_arg<torrent::Event*>(torrent::Event* lhs, torrent::Event* rhs) {
|
inline bool mock_compare_arg<torrent::system::Event*>(torrent::system::Event* lhs, torrent::system::Event* rhs) {
|
||||||
if (mock_compare_map<torrent::Event>::is_key(lhs)) {
|
if (mock_compare_map<torrent::system::Event>::is_key(lhs)) {
|
||||||
if (!mock_compare_map<torrent::Event>::has_value(rhs)) {
|
if (!mock_compare_map<torrent::system::Event>::has_value(rhs)) {
|
||||||
mock_compare_map<torrent::Event>::values[lhs] = rhs;
|
mock_compare_map<torrent::system::Event>::values[lhs] = rhs;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mock_compare_map<torrent::Event>::has_key(lhs) && mock_compare_map<torrent::Event>::get(lhs) == rhs;
|
return mock_compare_map<torrent::system::Event>::has_key(lhs) && mock_compare_map<torrent::system::Event>::get(lhs) == rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lhs == rhs;
|
return lhs == rhs;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "torrent/event.h"
|
|
||||||
#include "torrent/net/socket_address.h"
|
#include "torrent/net/socket_address.h"
|
||||||
#include "torrent/net/fd.h"
|
#include "torrent/net/fd.h"
|
||||||
|
#include "torrent/system/event.h"
|
||||||
#include "torrent/utils/log.h"
|
#include "torrent/utils/log.h"
|
||||||
#include "torrent/utils/random.h"
|
#include "torrent/utils/random.h"
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ mock_clear(bool ignore_assert) {
|
|||||||
MOCK_CLEANUP_MAP(torrent::random_uniform_uint16);
|
MOCK_CLEANUP_MAP(torrent::random_uniform_uint16);
|
||||||
MOCK_CLEANUP_MAP(torrent::random_uniform_uint32);
|
MOCK_CLEANUP_MAP(torrent::random_uniform_uint32);
|
||||||
|
|
||||||
mock_compare_map<torrent::Event>::values.clear();
|
mock_compare_map<torrent::system::Event>::values.clear();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -55,7 +55,9 @@ mock_redirect_defaults([[maybe_unused]] mock_redirect_flags flags) {
|
|||||||
|
|
||||||
mock_redirect(torrent::fd__close, std::function<int(int fildes)>([](int fildes) { return ::close(fildes); }));
|
mock_redirect(torrent::fd__close, std::function<int(int fildes)>([](int fildes) { return ::close(fildes); }));
|
||||||
mock_redirect(torrent::fd__fcntl_int, std::function<int(int fildes, int cmd, int arg)>([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); }));
|
mock_redirect(torrent::fd__fcntl_int, std::function<int(int fildes, int cmd, int arg)>([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); }));
|
||||||
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); }));
|
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) {
|
||||||
|
return ::setsockopt(socket, level, option_name, &option_value, sizeof(int));
|
||||||
|
}));
|
||||||
mock_redirect(torrent::fd__socket, std::function<int(int domain, int type, int protocol)>([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); }));
|
mock_redirect(torrent::fd__socket, std::function<int(int domain, int type, int protocol)>([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,69 +114,6 @@ int fd__socket(int domain, int type, int protocol) {
|
|||||||
return mock_call<int>(__func__, &torrent::fd__socket, domain, type, protocol);
|
return mock_call<int>(__func__, &torrent::fd__socket, domain, type, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Mock functions for 'torrent/common.h':
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace this_thread {
|
|
||||||
|
|
||||||
void event_open(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_open, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_open_and_count(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_open_and_count, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_close_and_count(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_close_and_count, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_closed_and_count(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_closed_and_count, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_insert_read(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_insert_read, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_insert_write(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_insert_write, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_insert_error(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_insert_error, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_remove_read(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_remove_read, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_remove_write(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_remove_write, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_remove_error(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_remove_error, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_remove_and_close(Event* event) {
|
|
||||||
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
|
|
||||||
return mock_call<void>(__func__, &torrent::this_thread::event_remove_and_close, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Mock functions for 'torrent/utils/random.h':
|
// Mock functions for 'torrent/utils/random.h':
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "torrent/exceptions.h"
|
#include "torrent/exceptions.h"
|
||||||
#include "torrent/net/resolver.h"
|
#include "torrent/net/resolver.h"
|
||||||
#include "torrent/utils/log.h"
|
#include "torrent/utils/log.h"
|
||||||
#include "torrent/utils/scheduler.h"
|
|
||||||
|
|
||||||
std::unique_ptr<TestMainThread>
|
std::unique_ptr<TestMainThread>
|
||||||
TestMainThread::create() {
|
TestMainThread::create() {
|
||||||
|
|||||||
@@ -169,20 +169,3 @@ TestParseOptions::test_flag_libtorrent() {
|
|||||||
|
|
||||||
FLAG_LT_LOG_ASSERT_ERROR("resume_data|rpc_dump");
|
FLAG_LT_LOG_ASSERT_ERROR("resume_data|rpc_dump");
|
||||||
}
|
}
|
||||||
|
|
||||||
#define FLAGS_LT_ENCRYPTION_ASSERT(flags, result) \
|
|
||||||
CPPUNIT_ASSERT(rpc::parse_option_flags(flags, std::bind(&torrent::option_find_string_str, torrent::OPTION_ENCRYPTION, std::placeholders::_1)) == (result))
|
|
||||||
|
|
||||||
#define FLAGS_LT_ENCRYPTION_ASSERT_ERROR(flags) \
|
|
||||||
ASSERT_CATCH_INPUT_ERROR(rpc::parse_option_flags(flags, std::bind(&torrent::option_find_string_str, torrent::OPTION_ENCRYPTION, std::placeholders::_1)))
|
|
||||||
|
|
||||||
void
|
|
||||||
TestParseOptions::test_flags_libtorrent() {
|
|
||||||
FLAGS_LT_ENCRYPTION_ASSERT("", torrent::runtime::NetworkConfig::encryption_none);
|
|
||||||
FLAGS_LT_ENCRYPTION_ASSERT("none", torrent::runtime::NetworkConfig::encryption_none);
|
|
||||||
FLAGS_LT_ENCRYPTION_ASSERT("require_rc4", torrent::runtime::NetworkConfig::encryption_require_RC4);
|
|
||||||
FLAGS_LT_ENCRYPTION_ASSERT("require_RC4", torrent::runtime::NetworkConfig::encryption_require_RC4);
|
|
||||||
FLAGS_LT_ENCRYPTION_ASSERT("require_RC4 | enable_retry", torrent::runtime::NetworkConfig::encryption_require_RC4 | torrent::runtime::NetworkConfig::encryption_enable_retry);
|
|
||||||
|
|
||||||
FLAGS_LT_ENCRYPTION_ASSERT_ERROR("require_");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class TestParseOptions : public test_fixture {
|
|||||||
CPPUNIT_TEST(test_flags_print_flags);
|
CPPUNIT_TEST(test_flags_print_flags);
|
||||||
|
|
||||||
CPPUNIT_TEST(test_flag_libtorrent);
|
CPPUNIT_TEST(test_flag_libtorrent);
|
||||||
CPPUNIT_TEST(test_flags_libtorrent);
|
|
||||||
|
|
||||||
CPPUNIT_TEST_SUITE_END();
|
CPPUNIT_TEST_SUITE_END();
|
||||||
|
|
||||||
@@ -30,5 +29,4 @@ public:
|
|||||||
void test_flags_print_flags();
|
void test_flags_print_flags();
|
||||||
|
|
||||||
void test_flag_libtorrent();
|
void test_flag_libtorrent();
|
||||||
void test_flags_libtorrent();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "test/src/test_command_local.h"
|
||||||
|
|
||||||
|
#include <torrent/torrent.h>
|
||||||
|
#include <torrent/runtime/socket_manager.h>
|
||||||
|
#include <torrent/utils/option_strings.h>
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "rpc/parse_commands.h"
|
||||||
|
|
||||||
|
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandLocal);
|
||||||
|
|
||||||
|
void initialize_command_local();
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandLocal::setUp() {
|
||||||
|
torrent::initialize_main_thread();
|
||||||
|
torrent::initialize();
|
||||||
|
|
||||||
|
if (control == nullptr)
|
||||||
|
control = new Control;
|
||||||
|
|
||||||
|
if (!rpc::commands.has("system.sockets.size"))
|
||||||
|
initialize_command_local();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandLocal::tearDown() {
|
||||||
|
torrent::cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandLocal::test_socket_category_commands() {
|
||||||
|
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
|
||||||
|
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
|
||||||
|
auto name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
|
||||||
|
|
||||||
|
for (const auto suffix : {".size", ".max_size", ".min_alloc", ".max_alloc"})
|
||||||
|
if (rpc::commands.has(name + suffix))
|
||||||
|
CPPUNIT_ASSERT_NO_THROW(rpc::commands.call(name + suffix));
|
||||||
|
|
||||||
|
const bool has_allocation = category != torrent::runtime::category_generic;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT(rpc::commands.has(name + ".size"));
|
||||||
|
CPPUNIT_ASSERT(rpc::commands.has(name + ".max_size"));
|
||||||
|
CPPUNIT_ASSERT(rpc::commands.has(name + ".min_alloc"));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc"));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc.set"));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc.set"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "test/helpers/test_fixture.h"
|
||||||
|
|
||||||
|
class TestCommandLocal : public test_fixture {
|
||||||
|
CPPUNIT_TEST_SUITE(TestCommandLocal);
|
||||||
|
|
||||||
|
CPPUNIT_TEST(test_socket_category_commands);
|
||||||
|
|
||||||
|
CPPUNIT_TEST_SUITE_END();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setUp();
|
||||||
|
void tearDown();
|
||||||
|
|
||||||
|
void test_socket_category_commands();
|
||||||
|
};
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "test/src/test_command_path.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <torrent/exceptions.h>
|
||||||
|
#include <torrent/torrent.h>
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "rpc/parse_commands.h"
|
||||||
|
|
||||||
|
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandPath);
|
||||||
|
|
||||||
|
void initialize_command_local();
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::setUp() {
|
||||||
|
char temp_dir[] = "/tmp/rtorrent_test_path_XXXXXX";
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
|
||||||
|
|
||||||
|
// The temporary directory itself may sit behind a symlink, as /tmp does on
|
||||||
|
// macOS, so resolve it up front to keep the expected values exact.
|
||||||
|
m_temp_dir = resolve_path(temp_dir);
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT(!m_temp_dir.empty());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data").c_str(), 0755));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/data").c_str(), (m_temp_dir + "/link").c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::tearDown() {
|
||||||
|
unlink((m_temp_dir + "/link").c_str());
|
||||||
|
rmdir((m_temp_dir + "/data").c_str());
|
||||||
|
rmdir(m_temp_dir.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::test_resolves_symlink() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/link"));
|
||||||
|
|
||||||
|
// A path that lies below a symlinked directory is resolved as well.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data/below").c_str(), 0755));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data/below", resolve_path(m_temp_dir + "/link/below"));
|
||||||
|
rmdir((m_temp_dir + "/data/below").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::test_removes_relative_components() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir, resolve_path(m_temp_dir + "/data/.."));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/./data"));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/data/"));
|
||||||
|
|
||||||
|
// Trailing slashes and duplicated separators collapse.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "//data//"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::test_expands_tilde() {
|
||||||
|
const char* home = std::getenv("HOME");
|
||||||
|
|
||||||
|
if (home == nullptr || *home == '\0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(resolve_path(home), resolve_path("~"));
|
||||||
|
CPPUNIT_ASSERT_THROW(resolve_path("~root/somewhere"), torrent::input_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::test_missing_path_throws() {
|
||||||
|
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(""), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(m_temp_dir + "/does_not_exist"), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path_or_throw(m_temp_dir + "/link"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::test_missing_path_is_empty() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(""));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist"));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist/below"));
|
||||||
|
|
||||||
|
// A dangling symlink does not name an existing path either.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/gone").c_str(), (m_temp_dir + "/dangling").c_str()));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/dangling"));
|
||||||
|
unlink((m_temp_dir + "/dangling").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandPath::test_commands() {
|
||||||
|
torrent::initialize_main_thread();
|
||||||
|
torrent::initialize();
|
||||||
|
|
||||||
|
if (control == nullptr)
|
||||||
|
control = new Control;
|
||||||
|
|
||||||
|
if (!rpc::commands.has("directory.default.realpath.or_empty"))
|
||||||
|
initialize_command_local();
|
||||||
|
|
||||||
|
rpc::commands.call_command("directory.default.set", m_temp_dir + "/link");
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/link", rpc::commands.call_command("directory.default", torrent::Object()).as_string());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()).as_string());
|
||||||
|
|
||||||
|
// A directory that has not been created yet resolves to nothing, and the
|
||||||
|
// or_throw variant reports it instead.
|
||||||
|
rpc::commands.call_command("directory.default.set", m_temp_dir + "/missing");
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(), rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
|
||||||
|
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()), torrent::input_error);
|
||||||
|
|
||||||
|
torrent::cleanup();
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "test/helpers/test_fixture.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class TestCommandPath : public test_fixture {
|
||||||
|
CPPUNIT_TEST_SUITE(TestCommandPath);
|
||||||
|
|
||||||
|
CPPUNIT_TEST(test_resolves_symlink);
|
||||||
|
CPPUNIT_TEST(test_removes_relative_components);
|
||||||
|
CPPUNIT_TEST(test_expands_tilde);
|
||||||
|
CPPUNIT_TEST(test_missing_path_is_empty);
|
||||||
|
CPPUNIT_TEST(test_missing_path_throws);
|
||||||
|
CPPUNIT_TEST(test_commands);
|
||||||
|
|
||||||
|
CPPUNIT_TEST_SUITE_END();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setUp();
|
||||||
|
void tearDown();
|
||||||
|
|
||||||
|
void test_resolves_symlink();
|
||||||
|
void test_removes_relative_components();
|
||||||
|
void test_expands_tilde();
|
||||||
|
void test_missing_path_is_empty();
|
||||||
|
void test_missing_path_throws();
|
||||||
|
void test_commands();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_temp_dir;
|
||||||
|
};
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "test/src/test_command_string.h"
|
||||||
|
|
||||||
|
#include "rpc/parse_commands.h"
|
||||||
|
|
||||||
|
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandString);
|
||||||
|
|
||||||
|
void initialize_command_string();
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
torrent::Object
|
||||||
|
args(std::initializer_list<torrent::Object> objects) {
|
||||||
|
auto result = torrent::Object::create_list();
|
||||||
|
|
||||||
|
for (const auto& object : objects)
|
||||||
|
result.as_list().push_back(object);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
call_string(const char* key, std::initializer_list<torrent::Object> objects) {
|
||||||
|
return rpc::commands.call_command(key, args(objects)).as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t
|
||||||
|
call_value(const char* key, std::initializer_list<torrent::Object> objects) {
|
||||||
|
return rpc::commands.call_command(key, args(objects)).as_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::Object::list_type
|
||||||
|
call_list(const char* key, std::initializer_list<torrent::Object> objects) {
|
||||||
|
return rpc::commands.call_command(key, args(objects)).as_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs a command the way a line in the configuration file would.
|
||||||
|
torrent::Object
|
||||||
|
parse(const char* command) {
|
||||||
|
return rpc::parse_command_single(rpc::make_target(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::setUp() {
|
||||||
|
if (!rpc::commands.has("string.length"))
|
||||||
|
initialize_command_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_length() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.length", {""}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(3), call_value("string.length", {"abc"}));
|
||||||
|
|
||||||
|
// The length is counted in utf-8 characters, not bytes.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(5), call_value("string.length", {"héllo"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(3), call_value("string.length", {"日本語"}));
|
||||||
|
|
||||||
|
// Values are converted to their string representation.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(4), call_value("string.length", {int64_t(1234)}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_equals() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {"abc", "abc"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "abd"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "ab"}));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {"abc", "x", "abc"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "x", "y"}));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {int64_t(42), "42"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_starts_with() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", "abc"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", ""}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.starts_with", {"abcdef", "bcd"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.starts_with", {"ab", "abc"}));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", "x", "ab"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_ends_with() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"abcdef", "def"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"abcdef", ""}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.ends_with", {"abcdef", "cde"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.ends_with", {"ef", "def"}));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"a.torrent", ".rar", ".torrent"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_contains() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains", {"abcdef", "cde"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains", {"abcdef", "ace"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains", {"abcdef", "x", "bcd"}));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains", {"retracker.local", "RETRACKER"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains_i", {"retracker.local", "RETRACKER"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains_i", {"RETRACKER.LOCAL", "retracker"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains_i", {"abcdef", "xyz"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_substr() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("abcdef"), call_string("string.substr", {"abcdef"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("cdef"), call_string("string.substr", {"abcdef", int64_t(2)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("cde"), call_string("string.substr", {"abcdef", int64_t(2), int64_t(3)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("cdef"), call_string("string.substr", {"abcdef", int64_t(2), int64_t(100)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"abcdef", int64_t(2), int64_t(0)}));
|
||||||
|
|
||||||
|
// Negative positions are relative to the end of the string.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("ef"), call_string("string.substr", {"abcdef", int64_t(-2)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("e"), call_string("string.substr", {"abcdef", int64_t(-2), int64_t(1)}));
|
||||||
|
|
||||||
|
// Out-of-bounds positions return the default value.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"abcdef", int64_t(10)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("n/a"), call_string("string.substr", {"abcdef", int64_t(10), int64_t(1), "n/a"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("n/a"), call_string("string.substr", {"abcdef", int64_t(-10), int64_t(1), "n/a"}));
|
||||||
|
|
||||||
|
// Positions and counts are in utf-8 characters, not bytes.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("本"), call_string("string.substr", {"日本語", int64_t(1), int64_t(1)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("本語"), call_string("string.substr", {"日本語", int64_t(-2)}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_split() {
|
||||||
|
auto parts = call_list("string.split", {"a,b,c", ","});
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(size_t(3), parts.size());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), parts.front().as_string());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("c"), parts.back().as_string());
|
||||||
|
|
||||||
|
// Empty fields are preserved.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(size_t(3), call_list("string.split", {"a,,b", ","}).size());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(size_t(1), call_list("string.split", {"abc", ","}).size());
|
||||||
|
|
||||||
|
// A multi-character delimiter is matched as a whole.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(size_t(2), call_list("string.split", {"a::b", "::"}).size());
|
||||||
|
|
||||||
|
// An empty delimiter splits the text into its utf-8 characters.
|
||||||
|
auto characters = call_list("string.split", {"日本語", ""});
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(size_t(3), characters.size());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("日"), characters.front().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_join() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a, b"), call_string("string.join", {", ", "a", "b"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("ab"), call_string("string.join", {"", "a", "b"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.join", {", "}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.join", {", ", "a"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("1-2"), call_string("string.join", {"-", int64_t(1), int64_t(2)}));
|
||||||
|
|
||||||
|
// Lists are flattened, so the output of string.split can be joined again.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), call_string("string.join", {"-", args({"a", "b"}), "c"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_pad() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(" 7"), call_string("string.lpad", {"7", int64_t(3)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("7 "), call_string("string.rpad", {"7", int64_t(3)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("007"), call_string("string.lpad", {"7", int64_t(3), "0"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("700"), call_string("string.rpad", {"7", int64_t(3), "0"}));
|
||||||
|
|
||||||
|
// Text that is already long enough is returned unchanged.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("abcd"), call_string("string.lpad", {"abcd", int64_t(2)}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("abcd"), call_string("string.rpad", {"abcd", int64_t(4)}));
|
||||||
|
|
||||||
|
// Multi-character padding is repeated, and an empty padding is a no-op.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("axyx"), call_string("string.rpad", {"a", int64_t(4), "xy"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.rpad", {"a", int64_t(4), ""}));
|
||||||
|
|
||||||
|
// Padding is counted in utf-8 characters, not bytes.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("00日"), call_string("string.lpad", {"日", int64_t(3), "0"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_strip() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a b"), call_string("string.strip", {" a b "}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"\t\n a \r\n"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a "), call_string("string.lstrip", {" a "}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(" a"), call_string("string.rstrip", {" a "}));
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"xxaxx", "x"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"/x/a/x/", "/", "x"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.strip", {"aaa", "a"}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string(" a "), call_string("string.strip", {" a ", ""}));
|
||||||
|
|
||||||
|
// The strippable argument is a set of utf-8 characters.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"日a日", "日"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_map() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("b"), call_string("string.map", {"a", args({"a", "b"})}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("c"), call_string("string.map", {"c", args({"a", "b"})}));
|
||||||
|
|
||||||
|
// Only whole-string matches are replaced.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("ab"), call_string("string.map", {"ab", args({"a", "b"})}));
|
||||||
|
|
||||||
|
// The first matching pair wins.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("y"), call_string("string.map", {"x", args({"a", "b"}), args({"x", "y"}), args({"x", "z"})}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_replace() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a+b+c"), call_string("string.replace", {"a-b-c", args({"-", "+"})}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("abc"), call_string("string.replace", {"a-b-c", args({"-", ""})}));
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), call_string("string.replace", {"a-b-c", args({"x", "y"})}));
|
||||||
|
|
||||||
|
// Pairs are applied in order, left to right.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("xby"), call_string("string.replace", {"abc", args({"a", "x"}), args({"c", "y"})}));
|
||||||
|
|
||||||
|
// A replacement that contains the replaced text terminates.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("aaaa"), call_string("string.replace", {"aa", args({"a", "aa"})}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_invalid_arguments() {
|
||||||
|
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("string.length", torrent::Object()), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_string("string.length", {"a", "b"}), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_value("string.equals", {"a"}), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_value("string.contains", {"a"}), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_list("string.split", {"a"}), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_string("string.lpad", {"a"}), torrent::input_error);
|
||||||
|
|
||||||
|
// The character count of string.substr cannot be negative.
|
||||||
|
CPPUNIT_ASSERT_THROW(call_string("string.substr", {"abc", int64_t(0), int64_t(-1)}), torrent::input_error);
|
||||||
|
|
||||||
|
// Both string.map and string.replace require {old, new} pairs.
|
||||||
|
CPPUNIT_ASSERT_THROW(call_string("string.map", {"a", "b"}), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_string("string.replace", {"a", args({"a", "b", "c"})}), torrent::input_error);
|
||||||
|
CPPUNIT_ASSERT_THROW(call_string("string.replace", {"a", args({"", "b"})}), torrent::input_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestCommandString::test_config_syntax() {
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(3), parse("string.length=abc").as_value());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), parse("string.contains=retracker.local,retracker").as_value());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(int64_t(1), parse("string.starts_with=udp://tracker.example.com,http://,udp://").as_value());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("cde"), parse("string.substr=abcdef,2,3").as_string());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("padded"), parse("string.strip=\" padded \"").as_string());
|
||||||
|
|
||||||
|
// The {old, new} pairs are written as a block in the configuration file.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a+b+c"), parse("string.replace=a-b-c,{-,+}").as_string());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("y"), parse("string.map=x,{a,b},{x,y}").as_string());
|
||||||
|
|
||||||
|
// Commands nest, so a split can be joined back together.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), parse("string.join=-,(string.split,a.b.c,.)").as_string());
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#include "test/helpers/test_fixture.h"
|
||||||
|
|
||||||
|
class TestCommandString : public test_fixture {
|
||||||
|
CPPUNIT_TEST_SUITE(TestCommandString);
|
||||||
|
|
||||||
|
CPPUNIT_TEST(test_length);
|
||||||
|
CPPUNIT_TEST(test_equals);
|
||||||
|
CPPUNIT_TEST(test_starts_with);
|
||||||
|
CPPUNIT_TEST(test_ends_with);
|
||||||
|
CPPUNIT_TEST(test_contains);
|
||||||
|
CPPUNIT_TEST(test_substr);
|
||||||
|
CPPUNIT_TEST(test_split);
|
||||||
|
CPPUNIT_TEST(test_join);
|
||||||
|
CPPUNIT_TEST(test_pad);
|
||||||
|
CPPUNIT_TEST(test_strip);
|
||||||
|
CPPUNIT_TEST(test_map);
|
||||||
|
CPPUNIT_TEST(test_replace);
|
||||||
|
CPPUNIT_TEST(test_invalid_arguments);
|
||||||
|
CPPUNIT_TEST(test_config_syntax);
|
||||||
|
|
||||||
|
CPPUNIT_TEST_SUITE_END();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setUp();
|
||||||
|
void tearDown();
|
||||||
|
|
||||||
|
void test_length();
|
||||||
|
void test_equals();
|
||||||
|
void test_starts_with();
|
||||||
|
void test_ends_with();
|
||||||
|
void test_contains();
|
||||||
|
void test_substr();
|
||||||
|
void test_split();
|
||||||
|
void test_join();
|
||||||
|
void test_pad();
|
||||||
|
void test_strip();
|
||||||
|
void test_map();
|
||||||
|
void test_replace();
|
||||||
|
void test_invalid_arguments();
|
||||||
|
void test_config_syntax();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user