mirror of
https://github.com/OPSnet/Ocelot.git
synced 2026-11-08 01:09:45 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
765b68550c | ||
|
|
7d57056e59 |
No files matched your search
@@ -18,5 +18,4 @@ CMakeFiles/
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
*.cbp
|
||||
.*.swp
|
||||
.DS_Store
|
||||
@@ -1,21 +1,3 @@
|
||||
-- 3.1.0 (2026-04-13)
|
||||
FEATURES
|
||||
* Ocelot now uses remote address of listen socket or X-Forwarded-For header as IP source.
|
||||
* Introduce an --ignore-xff flag to override the reading of said header.
|
||||
* IP addresses are maintained as 32bit ints internally rather than strings. This slims down the peer struct.
|
||||
* Peer keys are now hashed to a 64-bit value using the xxhash algorithm, removing further bloat from the peer struct.
|
||||
* Revamp how seeders and leechers are tracked per torrent.
|
||||
* Simplify how peers are selected.
|
||||
* Improved consistency of logging messages.
|
||||
* Removed hand-rolled inttostr().
|
||||
* Added const to methods and parameters where possible, some methods were made static. (cppcheck)
|
||||
* Added a report torrent endpoint (using libfmt).
|
||||
* Reduce memory reallocations in building db buffers.
|
||||
* Log level (error, info, debug) can be configured.
|
||||
BUGFIXES
|
||||
* When inserting a new torrent, set last_flushed to 0.
|
||||
* Ocelot used to "DELETE FROM torrents WHERE info_hash = ''". This no longer happens.
|
||||
|
||||
-- 3.0.0 (2026-02-15)
|
||||
BUILD
|
||||
* compile using the C++17 standard
|
||||
@@ -80,7 +62,7 @@ Add site_service config option to set the service/port ocelot runs on (defaults
|
||||
BUGFIXES
|
||||
* Fix FreeBSD support (thanks @HierraStrunger)
|
||||
BUILD
|
||||
* Drop Vagrantfile in favor of only supporting the Dockerfile
|
||||
* Drop Vagranfile in favor of only supporting the Dockerfile
|
||||
|
||||
-- 1.1 (2018-10-29)
|
||||
Move to spdlog (https://github.com/gabime/spdlog)to handle logging instead of cout
|
||||
|
||||
+1
-5
@@ -8,6 +8,7 @@ if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
message(FATAL_ERROR "Source and build directories cannot be the same.")
|
||||
endif()
|
||||
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@@ -49,9 +50,6 @@ find_package(MYSQLPP REQUIRED)
|
||||
find_package(LibEv REQUIRED)
|
||||
include_directories(${LIBEV_INCLUDE_DIR})
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
message(STATUS "Found libfmt version ${fmt_VERSION}")
|
||||
|
||||
include_directories(${CMAKE_SOURCE_DIR}/libs)
|
||||
|
||||
file(GLOB HEADERS src/*.h)
|
||||
@@ -62,7 +60,6 @@ add_executable(ocelot ${HEADERS} ${SOURCES})
|
||||
SET(LINK_LIBRARIES
|
||||
Boost::iostreams
|
||||
${LIBEV_LIBRARY}
|
||||
${LIBFMT_IBRARY}
|
||||
${MYSQLPP_LIBRARY}
|
||||
Threads::Threads
|
||||
)
|
||||
@@ -77,6 +74,5 @@ endif()
|
||||
|
||||
target_link_libraries(
|
||||
ocelot
|
||||
fmt::fmt
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
+1
-2
@@ -10,7 +10,6 @@ RUN apt-get update \
|
||||
libboost-iostreams-dev \
|
||||
libboost-system-dev \
|
||||
libev-dev \
|
||||
libfmt-dev \
|
||||
libjemalloc-dev \
|
||||
libmysql++-dev \
|
||||
netcat-traditional \
|
||||
@@ -20,7 +19,7 @@ RUN apt-get update \
|
||||
COPY . /srv
|
||||
WORKDIR /srv
|
||||
|
||||
RUN cmake -S /srv -B /srv/build \
|
||||
RUN cmake -Wno-dev -S /srv -B /srv/build \
|
||||
&& make -C build \
|
||||
&& apt-get purge -y \
|
||||
build-essential \
|
||||
|
||||
@@ -9,7 +9,6 @@ It supports requests over TCP and can only track IPv4 peers.
|
||||
* [GCC/G++](http://gcc.gnu.org/) (12+ recommended) or [Clang](https://clang.llvm.org/) (5+ minimum)
|
||||
* [Boost](http://www.boost.org/) (1.66+ required for `boost::asio::io_context`)
|
||||
* [libev](http://software.schmorp.de/pkg/libev.html) (4.0+ required)
|
||||
* [libfmt](https://github.com/fmtlib/fmt) (8.1+ required)
|
||||
* [MySQL++](http://tangentsoft.net/mysql++/) (3.2.0+ required)
|
||||
* [MySQL Client](https://dev.mysql.com/downloads/c-api/) (8.0+ recommended)
|
||||
* [jemalloc](http://jemalloc.net/) (5.0+ optional, but strongly recommended)
|
||||
@@ -26,7 +25,6 @@ sudo apt-get install \
|
||||
libboost-iostreams-dev \
|
||||
libboost-system-dev \
|
||||
libev-dev \
|
||||
libfmt-dev \
|
||||
libjemalloc-dev \
|
||||
libmysql++-dev \
|
||||
netcat-traditional \
|
||||
@@ -46,11 +44,9 @@ docker run -v $(pwd)/ocelot.conf:/srv/ocelot.conf ocelot
|
||||
|
||||
### Standalone Installation
|
||||
|
||||
* Build the binary
|
||||
* Prepare the build environment. (This must be re-executed when new source files are added).
|
||||
|
||||
cmake . -B build && make -C build
|
||||
|
||||
(Technically there is no need to run cmake if there are no library changes, but it is instant).
|
||||
cmake . -B build
|
||||
|
||||
* Create the following tables according to the [Gazelle database schema](https://raw.githubusercontent.com/WhatCD/Gazelle/master/gazelle.sql):
|
||||
- `torrents`
|
||||
@@ -64,25 +60,16 @@ docker run -v $(pwd)/ocelot.conf:/srv/ocelot.conf ocelot
|
||||
|
||||
* Edit `ocelot.conf` to your liking.
|
||||
|
||||
* Build Ocelot:
|
||||
|
||||
make -C build
|
||||
|
||||
## Running Ocelot
|
||||
|
||||
### Run-time options:
|
||||
|
||||
* `-c <path/to/ocelot.conf>` - Path to config file. If unspecified, the current working directory is used.
|
||||
* `-d` or `--daemonize` - Run as a daemon in the background.
|
||||
* `-v` - Print queue status every time a flush is initiated.
|
||||
* `-V` or `--version` - Print the version
|
||||
* `-X` or `--ignore-xff` - Ignore the X-Forwarded-For header
|
||||
|
||||
### Notes:
|
||||
|
||||
By default, ocelot will trust X-Forwarded-For headers as the source for client
|
||||
IP addresses. If ocelot is running behind a proxy, this is the desired behavior.
|
||||
(And if it is not, how are you performing SSL termination?)
|
||||
|
||||
If, for whatever reason, ocelot is not behind a proxy, this would allow an
|
||||
attacker to insert a bogus X-Forwarded-For header and set the IP address to a
|
||||
different IP address under their control. The `--ignore-xff` flag prevents this.
|
||||
|
||||
You can run a test ocelot daemon alongside a production daemon by specifying
|
||||
a separate configuration file, and setting `readonly = true`. This will
|
||||
|
||||
-2293
File diff suppressed because it is too large.
Load diff
+14
-23
@@ -1,4 +1,4 @@
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -16,15 +16,18 @@ confval::confval() {
|
||||
val_type = CONF_NONEXISTENT;
|
||||
}
|
||||
|
||||
confval::confval(bool value) : bool_val(value) {
|
||||
confval::confval(bool value) {
|
||||
bool_val = value;
|
||||
val_type = CONF_BOOL;
|
||||
}
|
||||
|
||||
confval::confval(unsigned int value) : uint_val(value) {
|
||||
confval::confval(unsigned int value) {
|
||||
uint_val = value;
|
||||
val_type = CONF_UINT;
|
||||
}
|
||||
|
||||
confval::confval(const char * value) : str_val(value) {
|
||||
confval::confval(const char * value) {
|
||||
str_val = value;
|
||||
val_type = CONF_STR;
|
||||
}
|
||||
|
||||
@@ -52,6 +55,7 @@ void confval::set(const std::string &value) {
|
||||
|
||||
config::config() {
|
||||
init();
|
||||
dummy_setting = new confval(); // Safety value to use if we're accessing nonexistent settings
|
||||
}
|
||||
|
||||
void config::init() {
|
||||
@@ -63,7 +67,6 @@ void config::init() {
|
||||
add("connection_timeout", 10u);
|
||||
add("keepalive_timeout", 0u);
|
||||
add("daemonize", false);
|
||||
add("ignore_xff", false);
|
||||
|
||||
// Tracker requests
|
||||
add("announce_interval", 1800u);
|
||||
@@ -94,8 +97,7 @@ void config::init() {
|
||||
add("report_password", "00000000000000000000000000000000");
|
||||
|
||||
// Logging
|
||||
add("log", true);
|
||||
add("log_level", "err");
|
||||
add("log", false);
|
||||
add("log_path", "ocelot"); // path to where to write log + name of log (don't need to put file extension)
|
||||
|
||||
add("report_path", "/tmp"); // path for transfer to website (should be a tmpfs in production)
|
||||
@@ -105,10 +107,10 @@ void config::init() {
|
||||
}
|
||||
|
||||
confval * config::get(const std::string &setting_name) {
|
||||
auto setting = settings.find(setting_name);
|
||||
const auto setting = settings.find(setting_name);
|
||||
if (setting == settings.end()) {
|
||||
spdlog::get("logger")->info("WARNING: Unrecognized setting '{}'", setting_name);
|
||||
return &dummy_setting;
|
||||
spdlog::get("logger")->info("WARNING: Unrecognized setting '" + setting_name + "'");
|
||||
return dummy_setting;
|
||||
}
|
||||
return &setting->second;
|
||||
}
|
||||
@@ -147,24 +149,13 @@ void config::load(std::istream &conf_file) {
|
||||
}
|
||||
|
||||
void config::reload() {
|
||||
std::string conf_file_path(get_str("conf_file_path"));
|
||||
const std::string conf_file_path(get_str("conf_file_path"));
|
||||
std::ifstream conf_file(conf_file_path);
|
||||
if (conf_file.fail()) {
|
||||
spdlog::get("logger")->error("config file '{}' could not be opened", conf_file_path);
|
||||
spdlog::get("logger")->error("Config file '" + conf_file_path + "' couldn't be opened");
|
||||
} else {
|
||||
init();
|
||||
load(conf_file);
|
||||
auto logger = spdlog::get("logger");
|
||||
if (get_str("log_level") == "debug") {
|
||||
logger->set_level(spdlog::level::debug);
|
||||
logger->flush_on(spdlog::level::debug);
|
||||
} else if (get_str("log_level") == "info") {
|
||||
logger->set_level(spdlog::level::info);
|
||||
logger->flush_on(spdlog::level::info);
|
||||
} else {
|
||||
logger->set_level(spdlog::level::err);
|
||||
logger->flush_on(spdlog::level::err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -25,17 +25,17 @@ class confval {
|
||||
uint32_t get_uint();
|
||||
bool get_bool();
|
||||
std::string get_str();
|
||||
void set(const std::string &value);
|
||||
void set(const std::string &val);
|
||||
};
|
||||
|
||||
class config {
|
||||
private:
|
||||
template <typename T> void add(const std::string &setting_name, T value);
|
||||
void init();
|
||||
std::string trim(const std::string str);
|
||||
void init();
|
||||
confval * get(const std::string &setting_name);
|
||||
std::map<std::string, confval> settings;
|
||||
confval dummy_setting;
|
||||
confval * dummy_setting;
|
||||
public:
|
||||
config();
|
||||
void load(std::istream &conf_file);
|
||||
|
||||
+169
-354
@@ -1,34 +1,31 @@
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fmt/format.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ocelot.h"
|
||||
#include "db.h"
|
||||
#include "user.h"
|
||||
#include "misc_functions.h"
|
||||
#include "config.h"
|
||||
|
||||
mysql::mysql(config * conf) :
|
||||
u_active(false),
|
||||
t_active(false),
|
||||
p_active(false),
|
||||
s_active(false),
|
||||
tok_active(false),
|
||||
logger(spdlog::get("logger")
|
||||
) {
|
||||
#define DB_LOCK_TIMEOUT 50
|
||||
|
||||
mysql::mysql(config * conf) : u_active(false), t_active(false), p_active(false), s_active(false), tok_active(false) {
|
||||
logger = spdlog::get("logger");
|
||||
load_config(conf);
|
||||
if (mysql_db.empty()) {
|
||||
logger->critical("No database configured");
|
||||
logger->error("No database selected");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,18 +34,10 @@ mysql::mysql(config * conf) :
|
||||
conn.set_option(&reconnect);
|
||||
conn.connect(mysql_db.c_str(), mysql_host.c_str(), mysql_username.c_str(), mysql_password.c_str(), mysql_port);
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
logger->critical("Failed to connect to MySQL: {}", er.what());
|
||||
logger->error("Failed to connect to MySQL (" + std::string(er.what()) + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// these are destined to grow very large
|
||||
heavy_peer_buffer.reserve(520000);
|
||||
light_peer_buffer.reserve(520000);
|
||||
snatch_buffer.reserve(65500);
|
||||
token_buffer.reserve(65500);
|
||||
torrent_buffer.reserve(65500);
|
||||
user_buffer.reserve(65500);
|
||||
|
||||
if (!readonly) {
|
||||
logger->info("Clearing xbt_files_users and resetting peer counts...");
|
||||
logger->flush();
|
||||
@@ -58,12 +47,12 @@ mysql::mysql(config * conf) :
|
||||
}
|
||||
|
||||
void mysql::load_config(config * conf) {
|
||||
mysql_db = conf->get_str("mysql_db");
|
||||
mysql_host = conf->get_str("mysql_host");
|
||||
mysql_db = conf->get_str("mysql_db");
|
||||
mysql_host = conf->get_str("mysql_host");
|
||||
mysql_username = conf->get_str("mysql_username");
|
||||
mysql_password = conf->get_str("mysql_password");
|
||||
mysql_port = conf->get_uint("mysql_port");
|
||||
readonly = conf->get_bool("readonly");
|
||||
mysql_port = conf->get_uint("mysql_port");
|
||||
readonly = conf->get_bool("readonly");
|
||||
}
|
||||
|
||||
void mysql::reload_config(config * conf) {
|
||||
@@ -91,16 +80,14 @@ void mysql::clear_peer_data() {
|
||||
logger->error("Unable to reset seeder and leecher count!");
|
||||
}
|
||||
} catch (const mysqlpp::BadQuery &er) {
|
||||
logger->error("bad query in clear_peer_data: {}", er.what());
|
||||
logger->error("Query error in clear_peer_data: " + std::string(er.what()));
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
logger->error("query execption in clear_peer_data: {}", er.what());
|
||||
logger->error("Query error in clear_peer_data: " + std::string(er.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void mysql::load_torrents(torrent_list &torrents) {
|
||||
mysqlpp::Query query = conn.query(
|
||||
"SELECT t.ID, t.info_hash, t.freetorrent, tls.Snatched FROM torrents t INNER JOIN torrents_leech_stats tls ON (tls.TorrentID = t.ID)"
|
||||
);
|
||||
mysqlpp::Query query = conn.query("SELECT t.ID, t.info_hash, t.freetorrent, tls.Snatched FROM torrents t INNER JOIN torrents_leech_stats tls ON (tls.TorrentID = t.ID) ORDER BY t.ID");
|
||||
try {
|
||||
mysqlpp::StoreQueryResult res = query.store();
|
||||
std::unordered_set<std::string> cur_keys;
|
||||
@@ -127,11 +114,10 @@ void mysql::load_torrents(torrent_list &torrents) {
|
||||
auto it = torrents.insert(std::pair<std::string, torrent>(info_hash, tmp_tor));
|
||||
torrent &tor = (it.first)->second;
|
||||
if (it.second) {
|
||||
tor.id = res[i][0];
|
||||
tor.completed = res[i][3];
|
||||
tor.balance = 0;
|
||||
tor.last_flushed = 0;
|
||||
tor.last_selected_seeder = 0;
|
||||
tor.id = res[i][0];
|
||||
tor.balance = 0;
|
||||
tor.completed = res[i][3];
|
||||
tor.last_selected_seeder = "";
|
||||
} else {
|
||||
tor.tokened_users.clear();
|
||||
cur_keys.erase(info_hash);
|
||||
@@ -161,17 +147,15 @@ void mysql::load_torrents(torrent_list &torrents) {
|
||||
}
|
||||
}
|
||||
} catch (const mysqlpp::BadQuery &er) {
|
||||
logger->error("Query error in load_torrents: {}", er.what());
|
||||
logger->error("Query error in load_torrents: " + std::string(er.what()));
|
||||
return;
|
||||
}
|
||||
logger->info("Loaded {} torrents", torrents.size());
|
||||
logger->info("Loaded " + std::to_string(torrents.size()) + " torrents");
|
||||
load_tokens(torrents);
|
||||
}
|
||||
|
||||
void mysql::load_users(user_list &users) {
|
||||
mysqlpp::Query query = conn.query(
|
||||
"SELECT ID, can_leech, torrent_pass, (Visible='0' OR IP='127.0.0.1') AS Protected FROM users_main WHERE Enabled='1'"
|
||||
);
|
||||
mysqlpp::Query query = conn.query("SELECT ID, can_leech, torrent_pass, (Visible='0' OR IP='127.0.0.1') AS Protected FROM users_main WHERE Enabled='1';");
|
||||
try {
|
||||
mysqlpp::StoreQueryResult res = query.store();
|
||||
size_t num_rows = res.num_rows();
|
||||
@@ -208,10 +192,10 @@ void mysql::load_users(user_list &users) {
|
||||
}
|
||||
}
|
||||
} catch (const mysqlpp::BadQuery &er) {
|
||||
logger->error("bad query in load_users: {}", er.what());
|
||||
logger->error("Query error in load_users: " + std::string(er.what()));
|
||||
return;
|
||||
}
|
||||
logger->info("Loaded {} users", users.size());
|
||||
logger->info("Loaded " + std::to_string(users.size()) + " users");
|
||||
}
|
||||
|
||||
void mysql::load_tokens(torrent_list &torrents) {
|
||||
@@ -232,15 +216,15 @@ void mysql::load_tokens(torrent_list &torrents) {
|
||||
}
|
||||
}
|
||||
} catch (const mysqlpp::BadQuery &er) {
|
||||
logger->error("Query error in load_tokens: {}", er.what());
|
||||
logger->error("Query error in load_tokens: " + std::string(er.what()));
|
||||
return;
|
||||
}
|
||||
logger->info("Loaded {} tokens", token_count);
|
||||
logger->info("Loaded " + std::to_string(token_count) + " tokens");
|
||||
}
|
||||
|
||||
|
||||
void mysql::load_whitelist(std::vector<std::string> &whitelist) {
|
||||
mysqlpp::Query query = conn.query("SELECT peer_id FROM xbt_client_whitelist");
|
||||
mysqlpp::Query query = conn.query("SELECT peer_id FROM xbt_client_whitelist;");
|
||||
try {
|
||||
mysqlpp::StoreQueryResult res = query.store();
|
||||
size_t num_rows = res.num_rows();
|
||||
@@ -252,129 +236,68 @@ void mysql::load_whitelist(std::vector<std::string> &whitelist) {
|
||||
whitelist.push_back(peer_id);
|
||||
}
|
||||
} catch (const mysqlpp::BadQuery &er) {
|
||||
logger->error("Query error in load_whitelist: {}", er.what());
|
||||
logger->error("Query error in load_whitelist: " + std::string(er.what()));
|
||||
return;
|
||||
}
|
||||
if (whitelist.size() == 0) {
|
||||
logger->info("Assuming no whitelist desired, disabling");
|
||||
} else {
|
||||
logger->info("Loaded {} clients into the whitelist", whitelist.size());
|
||||
logger->info("Loaded " + std::to_string(whitelist.size()) + " clients into the whitelist");
|
||||
}
|
||||
}
|
||||
|
||||
std::string mysql::escape(const std::string &value) {
|
||||
// this sucks, but the mysqlpp interface is utterly idiotic
|
||||
void mysql::record_token(const std::string &record) {
|
||||
if (!update_token_buffer.empty()) {
|
||||
update_token_buffer += ",";
|
||||
}
|
||||
update_token_buffer += record;
|
||||
}
|
||||
|
||||
void mysql::record_user(const std::string &record) {
|
||||
if (!update_user_buffer.empty()) {
|
||||
update_user_buffer += ",";
|
||||
}
|
||||
update_user_buffer += record;
|
||||
}
|
||||
|
||||
void mysql::record_torrent(const std::string &record) {
|
||||
std::lock_guard<std::mutex> tb_lock(torrent_buffer_lock);
|
||||
if (!update_torrent_buffer.empty()) {
|
||||
update_torrent_buffer += ",";
|
||||
}
|
||||
update_torrent_buffer += record;
|
||||
}
|
||||
|
||||
void mysql::record_peer(const std::string &record, const std::string &ip, const std::string &peer_id, const std::string &useragent) {
|
||||
if (!update_heavy_peer_buffer.empty()) {
|
||||
update_heavy_peer_buffer += ",";
|
||||
}
|
||||
mysqlpp::Query q = conn.query();
|
||||
q << mysqlpp::quote << value;
|
||||
return q.str();
|
||||
q << record << mysqlpp::quote << ip << ',' << mysqlpp::quote << peer_id << ',' << mysqlpp::quote << useragent << "," << time(NULL) << ')';
|
||||
|
||||
update_heavy_peer_buffer += q.str();
|
||||
}
|
||||
void mysql::record_peer(const std::string &record, const std::string &peer_id) {
|
||||
if (!update_light_peer_buffer.empty()) {
|
||||
update_light_peer_buffer += ",";
|
||||
}
|
||||
mysqlpp::Query q = conn.query();
|
||||
q << record << mysqlpp::quote << peer_id << ',' << time(NULL) << ')';
|
||||
|
||||
update_light_peer_buffer += q.str();
|
||||
}
|
||||
|
||||
void mysql::record_peer(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const bool active,
|
||||
const int64_t uploaded,
|
||||
const int64_t downloaded,
|
||||
const int64_t upspeed,
|
||||
const int64_t downspeed,
|
||||
const int64_t left,
|
||||
const int64_t corrupt,
|
||||
const uint32_t seed_time,
|
||||
const uint32_t announces,
|
||||
const std::string &addr,
|
||||
const std::string &peer_id,
|
||||
const std::string &useragent
|
||||
) {
|
||||
std::lock_guard<std::mutex> lock(peer_buffer_lock);
|
||||
fmt::format_to(
|
||||
std::back_inserter(heavy_peer_buffer),
|
||||
"({},{},{},"
|
||||
"{},{},{},{},{},{},"
|
||||
"{},{},{},"
|
||||
"{},{},{}),",
|
||||
user_id, torrent_id, active ? 1 : 0,
|
||||
uploaded, downloaded, upspeed, downspeed, left, corrupt,
|
||||
seed_time, announces, addr,
|
||||
escape(peer_id), escape(useragent), time(NULL)
|
||||
);
|
||||
void mysql::record_snatch(const std::string &record, const std::string &ip) {
|
||||
if (!update_snatch_buffer.empty()) {
|
||||
update_snatch_buffer += ",";
|
||||
}
|
||||
mysqlpp::Query q = conn.query();
|
||||
q << record << ',' << mysqlpp::quote << ip << ')';
|
||||
update_snatch_buffer += q.str();
|
||||
}
|
||||
|
||||
void mysql::record_peer(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const uint32_t seed_time,
|
||||
const uint32_t announces,
|
||||
const std::string &peer_id
|
||||
) {
|
||||
std::lock_guard<std::mutex> lock(peer_buffer_lock);
|
||||
fmt::format_to(
|
||||
std::back_inserter(light_peer_buffer),
|
||||
"({},{},{},{},{},{}),",
|
||||
user_id, torrent_id, announces, seed_time, escape(peer_id), time(NULL)
|
||||
);
|
||||
}
|
||||
|
||||
void mysql::record_snatch(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const std::string &addr
|
||||
) {
|
||||
std::lock_guard<std::mutex> lock(snatch_buffer_lock);
|
||||
fmt::format_to(
|
||||
std::back_inserter(snatch_buffer),
|
||||
"({},{},{},{}),",
|
||||
user_id, torrent_id, time(NULL), addr
|
||||
);
|
||||
}
|
||||
|
||||
void mysql::record_token(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const int64_t downloaded_change
|
||||
) {
|
||||
std::lock_guard<std::mutex> lock(token_buffer_lock);
|
||||
fmt::format_to(
|
||||
std::back_inserter(token_buffer),
|
||||
"({},{},{}),",
|
||||
user_id, torrent_id, downloaded_change
|
||||
);
|
||||
}
|
||||
|
||||
void mysql::record_torrent(
|
||||
const torid_t torrent_id,
|
||||
const uint32_t seeder_total,
|
||||
const uint32_t leecher_total,
|
||||
const bool completed,
|
||||
const int64_t balance
|
||||
) {
|
||||
std::lock_guard<std::mutex> lock(torrent_buffer_lock);
|
||||
fmt::format_to(
|
||||
std::back_inserter(torrent_buffer),
|
||||
"({},{},{},{},{}),",
|
||||
torrent_id, seeder_total, leecher_total, completed ? 1 : 0, balance
|
||||
);
|
||||
}
|
||||
|
||||
void mysql::record_user(
|
||||
const userid_t userid,
|
||||
const int64_t uploaded_change,
|
||||
const int64_t downloaded_change
|
||||
) {
|
||||
fmt::format_to(
|
||||
std::back_inserter(user_buffer),
|
||||
"({},{},{}),",
|
||||
userid, uploaded_change, downloaded_change
|
||||
);
|
||||
}
|
||||
|
||||
bool mysql::all_clear() const {
|
||||
return (
|
||||
user_queue.size() == 0
|
||||
&& torrent_queue.size() == 0
|
||||
&& peer_queue.size() == 0
|
||||
&& snatch_queue.size() == 0
|
||||
&& token_queue.size() == 0
|
||||
);
|
||||
bool mysql::all_clear() {
|
||||
return (user_queue.size() == 0 && torrent_queue.size() == 0 && peer_queue.size() == 0 && snatch_queue.size() == 0 && token_queue.size() == 0);
|
||||
}
|
||||
|
||||
void mysql::flush() {
|
||||
@@ -386,32 +309,23 @@ void mysql::flush() {
|
||||
}
|
||||
|
||||
void mysql::flush_users() {
|
||||
std::lock_guard<std::mutex> ub_lock(user_buffer_lock);
|
||||
if (readonly) {
|
||||
user_buffer = "";
|
||||
update_user_buffer.clear();
|
||||
return;
|
||||
}
|
||||
std::string sql;
|
||||
std::lock_guard<std::mutex> uq_lock(user_queue_lock);
|
||||
size_t qsize = user_queue.size();
|
||||
if (verbose_flush || qsize > 0) {
|
||||
logger->info(
|
||||
"flush_users() chunks: {}, first chunk len: {}",
|
||||
qsize, user_queue.front().size()
|
||||
);
|
||||
logger->info("User flush queue size: " + std::to_string(qsize) + ", next query length: " + std::to_string(user_queue.front().size()));
|
||||
}
|
||||
if (user_buffer.empty()) {
|
||||
if (update_user_buffer.empty()) {
|
||||
return;
|
||||
}
|
||||
user_buffer.pop_back(); // remove trailing comma
|
||||
user_queue.push(
|
||||
fmt::format(
|
||||
"INSERT INTO users_leech_stats (UserID,Uploaded,Downloaded)"
|
||||
"VALUES{}"
|
||||
"ON DUPLICATE KEY UPDATE Uploaded=Uploaded+VALUES(Uploaded),Downloaded=Downloaded+VALUES(Downloaded)",
|
||||
user_buffer
|
||||
)
|
||||
);
|
||||
user_buffer = "";
|
||||
sql = "INSERT INTO users_leech_stats (UserID, Uploaded, Downloaded) VALUES " + update_user_buffer +
|
||||
" ON DUPLICATE KEY UPDATE Uploaded = Uploaded + VALUES(Uploaded), Downloaded = Downloaded + VALUES(Downloaded)";
|
||||
user_queue.push(sql);
|
||||
update_user_buffer.clear();
|
||||
stats.user_queue_size = qsize + 1;
|
||||
if (!u_active) {
|
||||
std::thread thread(&mysql::do_flush_users, this);
|
||||
@@ -422,33 +336,28 @@ void mysql::flush_users() {
|
||||
void mysql::flush_torrents() {
|
||||
std::lock_guard<std::mutex> tb_lock(torrent_buffer_lock);
|
||||
if (readonly) {
|
||||
torrent_buffer = "";
|
||||
update_torrent_buffer.clear();
|
||||
return;
|
||||
}
|
||||
std::string sql;
|
||||
std::lock_guard<std::mutex> tq_lock(torrent_queue_lock);
|
||||
size_t qsize = torrent_queue.size();
|
||||
if (verbose_flush || qsize > 0) {
|
||||
logger->info(
|
||||
"flush_torrents() chunks: {}, first chunk len: {}",
|
||||
qsize, torrent_queue.front().size()
|
||||
);
|
||||
logger->info("Torrent flush queue size: " + std::to_string(qsize) + ", next query length: " + std::to_string(torrent_queue.front().size()));
|
||||
}
|
||||
if (torrent_buffer.empty()) {
|
||||
if (update_torrent_buffer.empty()) {
|
||||
return;
|
||||
}
|
||||
torrent_buffer.pop_back();
|
||||
torrent_queue.push(
|
||||
fmt::format(
|
||||
"INSERT INTO torrents_leech_stats(TorrentID,Seeders,Leechers,Snatched,Balance)"
|
||||
"VALUES{}"
|
||||
"ON DUPLICATE KEY UPDATE Seeders=VALUES(Seeders),Leechers=VALUES(Leechers),"
|
||||
"Snatched=Snatched+VALUES(Snatched),Balance=VALUES(Balance),last_action="
|
||||
"IF(VALUES(Seeders)>0,NOW(),last_action)",
|
||||
torrent_buffer
|
||||
)
|
||||
);
|
||||
stats.torrent_queue_size = qsize + 1;
|
||||
torrent_buffer = "";
|
||||
sql = "INSERT INTO torrents_leech_stats (TorrentID,Seeders,Leechers,Snatched,Balance) VALUES " + update_torrent_buffer +
|
||||
" ON DUPLICATE KEY UPDATE Seeders=VALUES(Seeders), Leechers=VALUES(Leechers), " +
|
||||
"Snatched=Snatched+VALUES(Snatched), Balance=VALUES(Balance), last_action = " +
|
||||
"IF(VALUES(Seeders) > 0, NOW(), last_action)";
|
||||
torrent_queue.push(sql);
|
||||
update_torrent_buffer.clear();
|
||||
sql.clear();
|
||||
sql = "DELETE FROM torrents WHERE info_hash = ''";
|
||||
torrent_queue.push(sql);
|
||||
stats.torrent_queue_size = qsize + 2;
|
||||
if (!t_active) {
|
||||
std::thread thread(&mysql::do_flush_torrents, this);
|
||||
thread.detach();
|
||||
@@ -456,30 +365,22 @@ void mysql::flush_torrents() {
|
||||
}
|
||||
|
||||
void mysql::flush_snatches() {
|
||||
std::lock_guard<std::mutex> sb_lock(snatch_buffer_lock);
|
||||
if (readonly) {
|
||||
snatch_buffer = "";
|
||||
update_snatch_buffer.clear();
|
||||
return;
|
||||
}
|
||||
std::string sql;
|
||||
std::lock_guard<std::mutex> sq_lock(snatch_queue_lock);
|
||||
size_t qsize = snatch_queue.size();
|
||||
if (verbose_flush || qsize > 0) {
|
||||
logger->info(
|
||||
"flush_snatches() chunks: {}, first chunk len={}",
|
||||
qsize, snatch_queue.front().size()
|
||||
);
|
||||
logger->info("Snatch flush queue size: " + std::to_string(qsize) + ", next query length: " + std::to_string(snatch_queue.front().size()));
|
||||
}
|
||||
if (snatch_buffer.empty()) {
|
||||
if (update_snatch_buffer.empty()) {
|
||||
return;
|
||||
}
|
||||
snatch_buffer.pop_back();
|
||||
snatch_queue.push(
|
||||
fmt::format(
|
||||
"INSERT IGNORE INTO xbt_snatched(uid,fid,tstamp,IP)VALUES{}",
|
||||
snatch_buffer
|
||||
)
|
||||
);
|
||||
snatch_buffer = "";
|
||||
sql = "INSERT IGNORE INTO xbt_snatched (uid, fid, tstamp, IP) VALUES " + update_snatch_buffer;
|
||||
snatch_queue.push(sql);
|
||||
update_snatch_buffer.clear();
|
||||
stats.snatch_queue_size = qsize + 1;
|
||||
if (!s_active) {
|
||||
std::thread thread(&mysql::do_flush_snatches, this);
|
||||
@@ -488,70 +389,59 @@ void mysql::flush_snatches() {
|
||||
}
|
||||
|
||||
void mysql::flush_peers() {
|
||||
std::lock_guard<std::mutex> pb_lock(peer_buffer_lock);
|
||||
if (readonly) {
|
||||
light_peer_buffer = "";
|
||||
heavy_peer_buffer = "";
|
||||
update_light_peer_buffer.clear();
|
||||
update_heavy_peer_buffer.clear();
|
||||
return;
|
||||
}
|
||||
std::string sql;
|
||||
std::lock_guard<std::mutex> pq_lock(peer_queue_lock);
|
||||
size_t qsize = peer_queue.size();
|
||||
short qsize_added = 0;
|
||||
if (verbose_flush || qsize > 0) {
|
||||
logger->info(
|
||||
"flush_peers() chunks: {}, first chunk length: {}",
|
||||
qsize, peer_queue.front().size()
|
||||
);
|
||||
logger->info("Peer flush queue size: " + std::to_string(qsize) + ", next query length: " + std::to_string(peer_queue.front().size()));
|
||||
}
|
||||
|
||||
// Nothing to do
|
||||
if (light_peer_buffer.empty() && heavy_peer_buffer.empty()) {
|
||||
if (update_light_peer_buffer.empty() && update_heavy_peer_buffer.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!heavy_peer_buffer.empty()) {
|
||||
if (!update_heavy_peer_buffer.empty()) {
|
||||
// Because xfu inserts are slow and ram is not infinite we need to
|
||||
// limit this queue's size
|
||||
// xfu will be messed up if the light query inserts a new row,
|
||||
// but that's better than an oom crash
|
||||
if (qsize >= 3000) {
|
||||
if (qsize >= 1000) {
|
||||
peer_queue.pop();
|
||||
} else {
|
||||
qsize_added += 1;
|
||||
}
|
||||
heavy_peer_buffer.pop_back();
|
||||
peer_queue.push(
|
||||
fmt::format(
|
||||
"INSERT INTO xbt_files_users(uid,fid,active,uploaded,downloaded,upspeed,downspeed,remaining,corrupt,timespent,announced,ip,peer_id,useragent,mtime)VALUES"
|
||||
"{}"
|
||||
"ON DUPLICATE KEY UPDATE active=VALUES(active),uploaded=VALUES(uploaded),"
|
||||
"downloaded=VALUES(downloaded),upspeed=VALUES(upspeed),"
|
||||
"downspeed=VALUES(downspeed),remaining=VALUES(remaining),"
|
||||
"corrupt=VALUES(corrupt),timespent=VALUES(timespent),"
|
||||
"announced=VALUES(announced),mtime=VALUES(mtime)",
|
||||
heavy_peer_buffer
|
||||
)
|
||||
);
|
||||
heavy_peer_buffer = "";
|
||||
sql = "INSERT INTO xbt_files_users (uid,fid,active,uploaded,downloaded,upspeed,downspeed,remaining,corrupt," +
|
||||
std::string("timespent,announced,ip,peer_id,useragent,mtime) VALUES ") + update_heavy_peer_buffer +
|
||||
" ON DUPLICATE KEY UPDATE active=VALUES(active), uploaded=VALUES(uploaded), " +
|
||||
"downloaded=VALUES(downloaded), upspeed=VALUES(upspeed), " +
|
||||
"downspeed=VALUES(downspeed), remaining=VALUES(remaining), " +
|
||||
"corrupt=VALUES(corrupt), timespent=VALUES(timespent), " +
|
||||
"announced=VALUES(announced), mtime=VALUES(mtime)";
|
||||
peer_queue.push(sql);
|
||||
update_heavy_peer_buffer.clear();
|
||||
sql.clear();
|
||||
}
|
||||
if (!light_peer_buffer.empty()) {
|
||||
if (!update_light_peer_buffer.empty()) {
|
||||
// See comment above
|
||||
if (qsize >= 3000) {
|
||||
if (qsize >= 1000) {
|
||||
peer_queue.pop();
|
||||
} else {
|
||||
qsize_added += 1;
|
||||
}
|
||||
light_peer_buffer.pop_back();
|
||||
peer_queue.push(
|
||||
fmt::format(
|
||||
"INSERT INTO xbt_files_users(uid,fid,timespent,announced,peer_id,mtime)"
|
||||
"VALUES{}"
|
||||
"ON DUPLICATE KEY UPDATE upspeed=0,downspeed=0,timespent=VALUES(timespent),"
|
||||
"announced=VALUES(announced),mtime=VALUES(mtime)",
|
||||
light_peer_buffer
|
||||
)
|
||||
);
|
||||
light_peer_buffer = "";
|
||||
sql = "INSERT INTO xbt_files_users (uid,fid,timespent,announced,peer_id,mtime) VALUES " +
|
||||
update_light_peer_buffer +
|
||||
" ON DUPLICATE KEY UPDATE upspeed=0, downspeed=0, timespent=VALUES(timespent), " +
|
||||
"announced=VALUES(announced), mtime=VALUES(mtime)";
|
||||
peer_queue.push(sql);
|
||||
update_light_peer_buffer.clear();
|
||||
sql.clear();
|
||||
}
|
||||
stats.peer_queue_size = qsize + qsize_added;
|
||||
|
||||
@@ -563,31 +453,22 @@ void mysql::flush_peers() {
|
||||
|
||||
void mysql::flush_tokens() {
|
||||
if (readonly) {
|
||||
token_buffer = "";
|
||||
update_token_buffer.clear();
|
||||
return;
|
||||
}
|
||||
std::string sql;
|
||||
std::lock_guard<std::mutex> tq_lock(token_queue_lock);
|
||||
size_t qsize = token_queue.size();
|
||||
if (verbose_flush || qsize > 0) {
|
||||
logger->info(
|
||||
"flush_tokens() chunks: {}, first chunk length: {}",
|
||||
qsize, token_queue.front().size()
|
||||
);
|
||||
|
||||
logger->info("Token flush queue size: " + std::to_string(qsize) + ", next query length: " + std::to_string(token_queue.front().size()));
|
||||
}
|
||||
if (token_buffer.empty()) {
|
||||
if (update_token_buffer.empty()) {
|
||||
return;
|
||||
}
|
||||
token_buffer.pop_back();
|
||||
token_queue.push(
|
||||
fmt::format(
|
||||
"INSERT INTO users_freeleeches(UserID,TorrentID,Downloaded)"
|
||||
"VALUES{}"
|
||||
"ON DUPLICATE KEY UPDATE Downloaded=Downloaded+VALUES(Downloaded)",
|
||||
token_buffer
|
||||
)
|
||||
);
|
||||
token_buffer = "";
|
||||
sql = "INSERT INTO users_freeleeches (UserID, TorrentID, Downloaded) VALUES " + update_token_buffer +
|
||||
" ON DUPLICATE KEY UPDATE Downloaded = Downloaded + VALUES(Downloaded)";
|
||||
token_queue.push(sql);
|
||||
update_token_buffer.clear();
|
||||
stats.token_queue_size = qsize + 1;
|
||||
if (!tok_active) {
|
||||
std::thread thread(&mysql::do_flush_tokens, this);
|
||||
@@ -599,14 +480,13 @@ void mysql::do_flush_users() {
|
||||
u_active = true;
|
||||
try {
|
||||
mysqlpp::Connection c = create_connection();
|
||||
std::string sql;
|
||||
while (user_queue.size() > 0) {
|
||||
try {
|
||||
sql = user_queue.front();
|
||||
std::string sql = user_queue.front();
|
||||
mysqlpp::Query query = c.query(sql);
|
||||
if (!query.exec()) {
|
||||
logger->info("User flush failed ({} remain)", user_queue.size());
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
logger->info("User flush failed (" + std::to_string(user_queue.size()) + " remain)");
|
||||
sleep(3);
|
||||
break;
|
||||
} else {
|
||||
std::lock_guard<std::mutex> uq_lock(user_queue_lock);
|
||||
@@ -615,31 +495,18 @@ void mysql::do_flush_users() {
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::BadQuery &er) {
|
||||
sql.resize(800);
|
||||
logger->error(
|
||||
"bad query: {} in do_flush_users, qlength: {}, chunks: {} sql={} --EOQ",
|
||||
er.what(),
|
||||
user_queue.front().size(),
|
||||
user_queue.size(),
|
||||
sql
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush users with a qlength: " + std::to_string(user_queue.front().size()) + " queue size: " + std::to_string(user_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
sql.resize(800);
|
||||
logger->error(
|
||||
"query exception: {} in do_flush_users, qlength: {}, chunks: {}",
|
||||
er.what(),
|
||||
user_queue.front().size(),
|
||||
user_queue.size()
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush users with a qlength: " + std::to_string(user_queue.front().size()) + " queue size: " + std::to_string(user_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::Exception &er) {
|
||||
logger->error("MySQL error in do_flush_users: {}", er.what());
|
||||
logger->error("MySQL error in flush_users: " + std::string(er.what()));
|
||||
}
|
||||
u_active = false;
|
||||
}
|
||||
@@ -648,10 +515,9 @@ void mysql::do_flush_torrents() {
|
||||
t_active = true;
|
||||
try {
|
||||
mysqlpp::Connection c = create_connection();
|
||||
std::string sql;
|
||||
while (torrent_queue.size() > 0) {
|
||||
try {
|
||||
sql = torrent_queue.front();
|
||||
std::string sql = torrent_queue.front();
|
||||
if (sql.empty()) {
|
||||
torrent_queue.pop();
|
||||
stats.torrent_queue_size -= 1;
|
||||
@@ -659,7 +525,7 @@ void mysql::do_flush_torrents() {
|
||||
}
|
||||
mysqlpp::Query query = c.query(sql);
|
||||
if (!query.exec()) {
|
||||
logger->info("do_flush_torrents failed ({} remain)", torrent_queue.size());
|
||||
logger->info("Torrent flush failed (" + std::to_string(torrent_queue.size()) + " remain)");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} else {
|
||||
@@ -669,30 +535,18 @@ void mysql::do_flush_torrents() {
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::BadQuery &er) {
|
||||
sql.resize(800);
|
||||
logger->error(
|
||||
"bad query: {} in do_flush_torrents, qlength: {}, chunks: {} sql={} --EOQ",
|
||||
er.what(),
|
||||
torrent_queue.front().size(),
|
||||
torrent_queue.size(),
|
||||
sql
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush torrents with a qlength: " + std::to_string(torrent_queue.front().size()) + " queue size: " + std::to_string(torrent_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
logger->error(
|
||||
"query exception: {} in do_flush_torrents, qlength: {}, chunks: {}",
|
||||
er.what(),
|
||||
torrent_queue.front().size(),
|
||||
torrent_queue.size()
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush torrents with a qlength: " + std::to_string(torrent_queue.front().size()) + " queue size: " + std::to_string(torrent_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::Exception &er) {
|
||||
logger->error("MySQL error in do_flush_torrents {}", er.what());
|
||||
logger->error("MySQL error in flush_torrents: " + std::string(er.what()));
|
||||
}
|
||||
t_active = false;
|
||||
}
|
||||
@@ -701,13 +555,12 @@ void mysql::do_flush_peers() {
|
||||
p_active = true;
|
||||
try {
|
||||
mysqlpp::Connection c = create_connection();
|
||||
std::string sql;
|
||||
while (peer_queue.size() > 0) {
|
||||
try {
|
||||
sql = peer_queue.front();
|
||||
std::string sql = peer_queue.front();
|
||||
mysqlpp::Query query = c.query(sql);
|
||||
if (!query.exec()) {
|
||||
logger->info("do_flush_peers failed ({} remain)", peer_queue.size());
|
||||
logger->info("Peer flush failed (" + std::to_string(peer_queue.size()) + " remain)");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} else {
|
||||
@@ -717,30 +570,18 @@ void mysql::do_flush_peers() {
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::BadQuery &er) {
|
||||
sql.resize(800);
|
||||
logger->error(
|
||||
"bad query: {} in do_flush_peers, qlength: {}, chunks: {} sql={} --EOQ",
|
||||
er.what(),
|
||||
peer_queue.front().size(),
|
||||
peer_queue.size(),
|
||||
sql
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush peers with a qlength: " + std::to_string(peer_queue.front().size()) + " queue size: " + std::to_string(peer_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
logger->error(
|
||||
"query exception: {} in do_flush_peers, qlength: {}, chunks: {}",
|
||||
er.what(),
|
||||
peer_queue.front().size(),
|
||||
peer_queue.size()
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush peers with a qlength: " + std::to_string(peer_queue.front().size()) + " queue size: " + std::to_string(peer_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::Exception &er) {
|
||||
logger->error("MySQL error in do_flush_peers: {}", er.what());
|
||||
logger->error("MySQL error in flush_peers: " + std::string(er.what()));
|
||||
}
|
||||
p_active = false;
|
||||
}
|
||||
@@ -749,13 +590,12 @@ void mysql::do_flush_snatches() {
|
||||
s_active = true;
|
||||
try {
|
||||
mysqlpp::Connection c = create_connection();
|
||||
std::string sql;
|
||||
while (snatch_queue.size() > 0) {
|
||||
try {
|
||||
sql = snatch_queue.front();
|
||||
std::string sql = snatch_queue.front();
|
||||
mysqlpp::Query query = c.query(sql);
|
||||
if (!query.exec()) {
|
||||
logger->info("do_flush_snatches failed ({} remain)", snatch_queue.size());
|
||||
logger->info("Snatch flush failed (" + std::to_string(snatch_queue.size()) + " remain)");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} else {
|
||||
@@ -765,30 +605,18 @@ void mysql::do_flush_snatches() {
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::BadQuery &er) {
|
||||
sql.resize(800);
|
||||
logger->error(
|
||||
"bad query: {} in do_flush_snatches, qlength: {}, chunks: {} sql={} --EOQ",
|
||||
er.what(),
|
||||
snatch_queue.front().size(),
|
||||
snatch_queue.size(),
|
||||
sql
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush snatches with a qlength: " + std::to_string(snatch_queue.front().size()) + " queue size: " + std::to_string(snatch_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
logger->error(
|
||||
"query exception: {} in do_flush_snatches, qlength: {}, chunks: {}",
|
||||
er.what(),
|
||||
snatch_queue.front().size(),
|
||||
snatch_queue.size()
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush snatches with a qlength: " + std::to_string(snatch_queue.front().size()) + " queue size: " + std::to_string(snatch_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::Exception &er) {
|
||||
logger->error("MySQL error in do_flush_snatches: {}", er.what());
|
||||
logger->error("MySQL error in flush_snatches: " + std::string(er.what()));
|
||||
}
|
||||
s_active = false;
|
||||
}
|
||||
@@ -797,13 +625,12 @@ void mysql::do_flush_tokens() {
|
||||
tok_active = true;
|
||||
try {
|
||||
mysqlpp::Connection c = create_connection();
|
||||
std::string sql;
|
||||
while (token_queue.size() > 0) {
|
||||
try {
|
||||
sql = token_queue.front();
|
||||
std::string sql = token_queue.front();
|
||||
mysqlpp::Query query = c.query(sql);
|
||||
if (!query.exec()) {
|
||||
logger->info("do_flush_snatches failed ({} remain)", token_queue.size());
|
||||
logger->info("Token flush failed (" + std::to_string(token_queue.size()) + " remain)");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} else {
|
||||
@@ -813,30 +640,18 @@ void mysql::do_flush_tokens() {
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::BadQuery &er) {
|
||||
sql.resize(800);
|
||||
logger->error(
|
||||
"bad query: {} in do_flush_tokens, qlength: {}, chunks: {} sql={} --EOQ",
|
||||
er.what(),
|
||||
token_queue.front().size(),
|
||||
token_queue.size(),
|
||||
sql
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush tokens with a qlength: " + std::to_string(token_queue.front().size()) + " queue size: " + std::to_string(token_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
} catch (const mysqlpp::Exception &er) {
|
||||
logger->error(
|
||||
"query exception: {} in do_flush_tokens, qlength: {}, chunks: {}",
|
||||
er.what(),
|
||||
token_queue.front().size(),
|
||||
token_queue.size()
|
||||
);
|
||||
logger->error("Query error: " + std::string(er.what()) + " in flush tokens with a qlength: " + std::to_string(token_queue.front().size()) + " queue size: " + std::to_string(token_queue.size()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const mysqlpp::Exception &er) {
|
||||
logger->error("MySQL error in do_flush_tokens: {}", er.what());
|
||||
logger->error("MySQL error in flush_tokens: " + std::string(er.what()));
|
||||
}
|
||||
tok_active = false;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef SRC_DB_H_
|
||||
#define SRC_DB_H_
|
||||
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#pragma GCC visibility push(default)
|
||||
|
||||
@@ -17,19 +17,18 @@
|
||||
class mysql {
|
||||
private:
|
||||
mysqlpp::Connection conn;
|
||||
std::string update_user_buffer;
|
||||
std::string update_torrent_buffer;
|
||||
std::string update_heavy_peer_buffer;
|
||||
std::string update_light_peer_buffer;
|
||||
std::string update_snatch_buffer;
|
||||
std::string update_token_buffer;
|
||||
|
||||
std::string heavy_peer_buffer;
|
||||
std::string light_peer_buffer;
|
||||
std::string snatch_buffer;
|
||||
std::string token_buffer;
|
||||
std::string torrent_buffer;
|
||||
std::string user_buffer;
|
||||
|
||||
std::queue<std::string> user_queue;
|
||||
std::queue<std::string> torrent_queue;
|
||||
std::queue<std::string> peer_queue;
|
||||
std::queue<std::string> snatch_queue;
|
||||
std::queue<std::string> token_queue;
|
||||
std::queue<std::string> torrent_queue;
|
||||
std::queue<std::string> user_queue;
|
||||
|
||||
std::string mysql_db, mysql_host, mysql_username, mysql_password;
|
||||
unsigned int mysql_port;
|
||||
@@ -38,17 +37,12 @@ class mysql {
|
||||
|
||||
// These locks prevent more than one thread from reading/writing the buffers.
|
||||
// These should be held for the minimum time possible.
|
||||
std::mutex peer_buffer_lock;
|
||||
std::mutex snatch_buffer_lock;
|
||||
std::mutex token_buffer_lock;
|
||||
std::mutex user_queue_lock;
|
||||
std::mutex torrent_buffer_lock;
|
||||
std::mutex user_buffer_lock;
|
||||
|
||||
std::mutex torrent_queue_lock;
|
||||
std::mutex peer_queue_lock;
|
||||
std::mutex snatch_queue_lock;
|
||||
std::mutex token_queue_lock;
|
||||
std::mutex torrent_queue_lock;
|
||||
std::mutex user_queue_lock;
|
||||
|
||||
std::shared_ptr<spdlog::logger> logger;
|
||||
|
||||
@@ -56,7 +50,6 @@ class mysql {
|
||||
void load_tokens(torrent_list &torrents);
|
||||
mysqlpp::Connection create_connection();
|
||||
|
||||
std::string escape(const std::string &value);
|
||||
void do_flush_users();
|
||||
void do_flush_torrents();
|
||||
void do_flush_snatches();
|
||||
@@ -77,61 +70,27 @@ class mysql {
|
||||
void reload_config(config * conf);
|
||||
void flush();
|
||||
bool connected();
|
||||
bool all_clear() const;
|
||||
bool all_clear();
|
||||
void load_torrents(torrent_list &torrents);
|
||||
void load_users(user_list &users);
|
||||
void load_whitelist(std::vector<std::string> &whitelist);
|
||||
|
||||
void record_peer(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const bool active,
|
||||
const int64_t uploaded,
|
||||
const int64_t downloaded,
|
||||
const int64_t upspeed,
|
||||
const int64_t downspeed,
|
||||
const int64_t left,
|
||||
const int64_t corrupt,
|
||||
const uint32_t seed_time,
|
||||
const uint32_t announces,
|
||||
const std::string &addr,
|
||||
const std::string &peer_id,
|
||||
const std::string &useragent
|
||||
);
|
||||
|
||||
void record_peer(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const uint32_t seed_time,
|
||||
const uint32_t announces,
|
||||
const std::string &peer_id
|
||||
);
|
||||
// (id,uploaded_change,downloaded_change)
|
||||
void record_user(const std::string &record);
|
||||
|
||||
void record_snatch(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const std::string &addr
|
||||
);
|
||||
// (id,seeders,leechers,snatched_change,balance)
|
||||
void record_torrent(const std::string &record);
|
||||
|
||||
void record_token(
|
||||
const userid_t user_id,
|
||||
const torid_t torrent_id,
|
||||
const int64_t downloaded_change
|
||||
);
|
||||
// (uid,fid,tstamp)
|
||||
void record_snatch(const std::string &record, const std::string &ip);
|
||||
|
||||
void record_torrent(
|
||||
const torid_t torrent_id,
|
||||
const uint32_t seeder_total,
|
||||
const uint32_t leecher_total,
|
||||
const bool completed,
|
||||
const int64_t balance
|
||||
);
|
||||
// (uid,fid,active,peerid,useragent,ip,uploaded,downloaded,upspeed,downspeed,left,timespent,announces,tstamp)
|
||||
void record_peer(const std::string &record, const std::string &ip, const std::string &peer_id, const std::string &useragent);
|
||||
|
||||
void record_user(
|
||||
const userid_t user_id,
|
||||
const int64_t uploaded_change,
|
||||
const int64_t downloaded_change
|
||||
);
|
||||
// (fid,peerid,timespent,announces,tstamp)
|
||||
void record_peer(const std::string &record, const std::string &peer_id);
|
||||
|
||||
void record_token(const std::string &record);
|
||||
|
||||
std::mutex torrent_list_mutex;
|
||||
std::mutex user_list_mutex;
|
||||
|
||||
+42
-42
@@ -18,7 +18,9 @@ std::mutex peak_open_mutex;
|
||||
|
||||
//---------- Connection mother - spawns middlemen and lets them deal with the connection
|
||||
|
||||
connection_mother::connection_mother(config * conf, worker * w, site_comm * sc_obj, schedule * sched) : work(w), logger(spdlog::get("logger")) {
|
||||
connection_mother::connection_mother(config * conf, worker * worker_obj, site_comm * sc_obj, schedule * sched) : work(worker_obj) {
|
||||
logger = spdlog::get("logger");
|
||||
|
||||
// Handle config stuff first
|
||||
load_config(conf);
|
||||
|
||||
@@ -26,7 +28,7 @@ connection_mother::connection_mother(config * conf, worker * w, site_comm * sc_o
|
||||
|
||||
// We failed to create the socket, so no point in running Ocelot
|
||||
if (listen_socket == 0) {
|
||||
logger->critical("failed to create listen socket for Ocelot, exiting");
|
||||
logger->critical("Failed to create socket for Ocelot. Exiting.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -38,13 +40,13 @@ connection_mother::connection_mother(config * conf, worker * w, site_comm * sc_o
|
||||
}
|
||||
|
||||
void connection_mother::load_config(config * conf) {
|
||||
listen_port = conf->get_uint("listen_port");
|
||||
max_connections = conf->get_uint("max_connections");
|
||||
max_middlemen = conf->get_uint("max_middlemen");
|
||||
listen_port = conf->get_uint("listen_port");
|
||||
max_connections = conf->get_uint("max_connections");
|
||||
max_middlemen = conf->get_uint("max_middlemen");
|
||||
connection_timeout = conf->get_uint("connection_timeout");
|
||||
keepalive_timeout = conf->get_uint("keepalive_timeout");
|
||||
max_read_buffer = conf->get_uint("max_read_buffer");
|
||||
max_request_size = conf->get_uint("max_request_size");
|
||||
keepalive_timeout = conf->get_uint("keepalive_timeout");
|
||||
max_read_buffer = conf->get_uint("max_read_buffer");
|
||||
max_request_size = conf->get_uint("max_request_size");
|
||||
}
|
||||
|
||||
void connection_mother::reload_config(config * conf) {
|
||||
@@ -52,10 +54,7 @@ void connection_mother::reload_config(config * conf) {
|
||||
unsigned int old_max_connections = max_connections;
|
||||
load_config(conf);
|
||||
if (old_listen_port != listen_port) {
|
||||
logger->info(
|
||||
"changing listen port from {} to {}",
|
||||
old_listen_port, listen_port
|
||||
);
|
||||
logger->info("Changing listen port from " + std::to_string(old_listen_port) +" to " + std::to_string(listen_port));
|
||||
int new_listen_socket = create_listen_socket();
|
||||
if (new_listen_socket != 0) {
|
||||
listen_event.stop();
|
||||
@@ -63,7 +62,7 @@ void connection_mother::reload_config(config * conf) {
|
||||
close(listen_socket);
|
||||
listen_socket = new_listen_socket;
|
||||
} else {
|
||||
logger->error("failed to create new listen socket when reloading config");
|
||||
logger->error("Couldn't create new listen socket when reloading config");
|
||||
}
|
||||
} else if (old_max_connections != max_connections) {
|
||||
listen(listen_socket, max_connections);
|
||||
@@ -71,13 +70,14 @@ void connection_mother::reload_config(config * conf) {
|
||||
}
|
||||
|
||||
int connection_mother::create_listen_socket() {
|
||||
sockaddr_in address = {0};
|
||||
sockaddr_in address;
|
||||
memset(&address, 0, sizeof(address));
|
||||
int new_listen_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
// Stop old sockets from hogging the port
|
||||
int yes = 1;
|
||||
if (setsockopt(new_listen_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
|
||||
logger->error("failed to reuse socket: {}", strerror(errno));
|
||||
logger->error("Could not reuse socket: " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -87,25 +87,25 @@ int connection_mother::create_listen_socket() {
|
||||
address.sin_port = htons(listen_port);
|
||||
|
||||
// Bind
|
||||
if (bind(new_listen_socket, reinterpret_cast<struct sockaddr*>(&address), sizeof(address)) == -1) {
|
||||
logger->error("failed to bind to listen socket: {}", strerror(errno));
|
||||
if (bind(new_listen_socket, (sockaddr *) &address, sizeof(address)) == -1) {
|
||||
logger->error("Bind failed: " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Listen
|
||||
if (listen(new_listen_socket, max_connections) == -1) {
|
||||
logger->error("failed to listen on socket: {}", strerror(errno));
|
||||
logger->error("Listen failed: " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set non-blocking
|
||||
int flags = fcntl(new_listen_socket, F_GETFL);
|
||||
if (flags == -1) {
|
||||
logger->error("failed to read socket flags: {}", strerror(errno));
|
||||
logger->error("Could not get socket flags: " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
if (fcntl(new_listen_socket, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
logger->error("failed to set listen socket non-blocking: {}", strerror(errno));
|
||||
logger->error("Could not set non-blocking: " + std::string(strerror(errno)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ int connection_mother::create_listen_socket() {
|
||||
}
|
||||
|
||||
void connection_mother::run() {
|
||||
logger->info("listening on port {}, starting event loop!", listen_port);
|
||||
logger->info("Sockets up on port " + std::to_string(listen_port) + ", starting event loop!");
|
||||
ev_loop(ev_default_loop(0), 0);
|
||||
}
|
||||
|
||||
@@ -140,12 +140,13 @@ connection_mother::~connection_mother()
|
||||
|
||||
//---------- Connection middlemen - these little guys live until their connection is closed
|
||||
|
||||
connection_middleman::connection_middleman(int &listen_socket, worker * w, connection_mother * mother_arg) :
|
||||
written(0), mother(mother_arg), work(w)
|
||||
connection_middleman::connection_middleman(int &listen_socket, worker * new_work, connection_mother * mother_arg) :
|
||||
written(0), mother(mother_arg), work(new_work)
|
||||
{
|
||||
auto logger = spdlog::get("logger");
|
||||
connect_sock = accept(listen_socket, NULL, NULL);
|
||||
if (connect_sock == -1) {
|
||||
spdlog::get("logger")->error("socket accept failed, errno {} ({})", errno, strerror(errno));
|
||||
logger->error("Accept failed, errno " + std::to_string(errno) + ": " + std::string(strerror(errno)));
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
@@ -153,10 +154,10 @@ connection_middleman::connection_middleman(int &listen_socket, worker * w, conne
|
||||
// Set non-blocking
|
||||
int flags = fcntl(connect_sock, F_GETFL);
|
||||
if (flags == -1) {
|
||||
spdlog::get("logger")->warn("failed to read connect socket flags");
|
||||
logger->warn("Could not get connect socket flags");
|
||||
}
|
||||
if (fcntl(connect_sock, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
spdlog::get("logger")->warn("failed to set connect socket non-blocking");
|
||||
logger->warn("Could not set non-blocking");
|
||||
}
|
||||
|
||||
// Get their info
|
||||
@@ -180,14 +181,14 @@ connection_middleman::~connection_middleman() {
|
||||
// Handler to read data from the socket, called by event loop when socket is readable
|
||||
void connection_middleman::handle_read(ev::io &watcher, int events_flags) {
|
||||
char buffer[mother->max_read_buffer + 1] = {0};
|
||||
int bytes_read = recv(connect_sock, &buffer, mother->max_read_buffer, 0);
|
||||
int ret = recv(connect_sock, &buffer, mother->max_read_buffer, 0);
|
||||
|
||||
if (bytes_read <= 0) {
|
||||
if (ret <= 0) {
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
stats.bytes_read += bytes_read;
|
||||
request.append(buffer, bytes_read);
|
||||
stats.bytes_read += ret;
|
||||
request.append(buffer, ret);
|
||||
size_t request_size = request.size();
|
||||
if (request_size > mother->max_request_size || (request_size >= 4 && request.compare(request_size - 4, std::string::npos, "\r\n\r\n") == 0)) {
|
||||
stats.requests++;
|
||||
@@ -200,13 +201,17 @@ void connection_middleman::handle_read(ev::io &watcher, int events_flags) {
|
||||
shutdown(connect_sock, SHUT_RD);
|
||||
response = error("GET string too long", client_opts);
|
||||
} else {
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
sockaddr_in client_addr;
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
getpeername(connect_sock, reinterpret_cast<sockaddr *>(&client_addr), &addr_len);
|
||||
getpeername(connect_sock, (sockaddr *) &client_addr, &addr_len);
|
||||
inet_ntop(AF_INET, &(client_addr.sin_addr), ip, INET_ADDRSTRLEN);
|
||||
std::string ip_str = ip;
|
||||
|
||||
//--- CALL WORKER
|
||||
response = work->work(request, client_addr.sin_addr.s_addr, client_opts);
|
||||
response = work->work(request, ip_str, client_opts);
|
||||
request.clear();
|
||||
request_size = 0;
|
||||
}
|
||||
|
||||
// Find out when the socket is writeable.
|
||||
@@ -218,17 +223,12 @@ void connection_middleman::handle_read(ev::io &watcher, int events_flags) {
|
||||
|
||||
// Handler to write data to the socket, called by event loop when socket is writeable
|
||||
void connection_middleman::handle_write(ev::io &watcher, int events_flags) {
|
||||
int bytes_written = send(
|
||||
connect_sock,
|
||||
response.c_str() + written,
|
||||
response.size() - written,
|
||||
MSG_NOSIGNAL
|
||||
);
|
||||
if (bytes_written == -1) {
|
||||
int ret = send(connect_sock, response.c_str()+written, response.size()-written, MSG_NOSIGNAL);
|
||||
if (ret == -1) {
|
||||
return;
|
||||
}
|
||||
stats.bytes_written += bytes_written;
|
||||
written += bytes_written;
|
||||
stats.bytes_written += ret;
|
||||
written += ret;
|
||||
if (written == response.size()) {
|
||||
write_event.stop();
|
||||
if (client_opts.http_close) {
|
||||
@@ -243,7 +243,7 @@ void connection_middleman::handle_write(ev::io &watcher, int events_flags) {
|
||||
}
|
||||
}
|
||||
|
||||
// After a middleman has been alive for timeout_interval seconds, this is called
|
||||
// After a middleman has been alive for timout_interval seconds, this is called
|
||||
void connection_middleman::handle_timeout(ev::timer &watcher, int events_flags) {
|
||||
timeout_event.stop();
|
||||
read_event.stop();
|
||||
|
||||
+2
-2
@@ -62,7 +62,7 @@ class connection_mother {
|
||||
std::shared_ptr<spdlog::logger> logger;
|
||||
|
||||
public:
|
||||
connection_mother(config * conf, worker * w, site_comm * sc_obj, schedule * sched);
|
||||
connection_mother(config * conf, worker * worker_obj, site_comm * sc_obj, schedule * sched_obj);
|
||||
~connection_mother();
|
||||
void reload_config(config * conf);
|
||||
int create_listen_socket();
|
||||
@@ -94,7 +94,7 @@ class connection_middleman {
|
||||
worker * work;
|
||||
|
||||
public:
|
||||
connection_middleman(int &listen_socket, worker * w, connection_mother * mother_arg);
|
||||
connection_middleman(int &listen_socket, worker* work, connection_mother * mother_arg);
|
||||
~connection_middleman();
|
||||
|
||||
void handle_read(ev::io &watcher, int events_flags);
|
||||
|
||||
+10
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -20,6 +20,15 @@ int64_t strtoint64(const std::string& str) {
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
std::string inttostr(const int i) {
|
||||
std::string str;
|
||||
std::stringstream out;
|
||||
out << i;
|
||||
str = out.str();
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string hex_decode(const std::string &in) {
|
||||
std::string out;
|
||||
out.reserve(20);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#ifndef SRC_MISC_FUNCTIONS_H_
|
||||
#define SRC_MISC_FUNCTIONS_H_
|
||||
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <string>
|
||||
|
||||
int32_t strtoint32(const std::string& str);
|
||||
int64_t strtoint64(const std::string& str);
|
||||
std::string inttostr(int i);
|
||||
std::string hex_decode(const std::string &in);
|
||||
std::string bintohex(const std::string &in);
|
||||
|
||||
|
||||
+9
-23
@@ -93,22 +93,19 @@ int main(int argc, char **argv) {
|
||||
// we don't use printf so make cout/cerr a little bit faster
|
||||
std::ios_base::sync_with_stdio(false);
|
||||
|
||||
bool conf_arg = false;
|
||||
bool daemonize = false;
|
||||
bool ignore_xff = false;
|
||||
bool verbose = false;
|
||||
|
||||
std::string conf_file_path("./ocelot.conf");
|
||||
conf = new config();
|
||||
|
||||
bool conf_arg = false;
|
||||
bool daemonize = false;
|
||||
bool verbose = false;
|
||||
std::string conf_file_path("./ocelot.conf");
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-v") == 0) {
|
||||
verbose = true;
|
||||
} else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--daemonize") == 0) {
|
||||
daemonize = true;
|
||||
} else if (strcmp(argv[i], "-X") == 0 || strcmp(argv[i], "--ignore-xff") == 0) {
|
||||
ignore_xff = true;
|
||||
} else if (i < argc - 1 && strcmp(argv[i], "-c") == 0) {
|
||||
} else if (strcmp(argv[i], "-c") == 0 && i < argc - 1) {
|
||||
conf_arg = true;
|
||||
conf_file_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
|
||||
@@ -131,10 +128,6 @@ int main(int argc, char **argv) {
|
||||
conf->load(conf_file_path, conf_file);
|
||||
}
|
||||
|
||||
if (ignore_xff) {
|
||||
conf->set("ignore_xff", "true");
|
||||
}
|
||||
|
||||
if (conf->get_bool("daemonize") || daemonize) {
|
||||
create_daemon();
|
||||
}
|
||||
@@ -148,22 +141,15 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
auto combined_logger = std::make_shared<spdlog::logger>("logger", begin(sinks), end(sinks));
|
||||
if (conf->get_str("log_level") == "debug") {
|
||||
combined_logger->set_level(spdlog::level::debug);
|
||||
combined_logger->flush_on(spdlog::level::debug);
|
||||
} else if (conf->get_str("log_level") == "info") {
|
||||
combined_logger->set_level(spdlog::level::info);
|
||||
combined_logger->flush_on(spdlog::level::info);
|
||||
} else {
|
||||
combined_logger->set_level(spdlog::level::err);
|
||||
combined_logger->flush_on(spdlog::level::err);
|
||||
}
|
||||
// If we don't set flush on info, the file log takes a long while to actually flush
|
||||
combined_logger->flush_on(spdlog::level::info);
|
||||
combined_logger->info(
|
||||
"Ocelot version " OCELOT_VERSION ", compiled " __DATE__ " " __TIME__
|
||||
);
|
||||
spdlog::register_logger(combined_logger);
|
||||
|
||||
db = new mysql(conf);
|
||||
|
||||
if (!db->connected()) {
|
||||
combined_logger->critical("Could not connect to DB. Exiting!");
|
||||
return 0;
|
||||
|
||||
+30
-12
@@ -4,26 +4,44 @@
|
||||
// Copyright [2017-2026] Orpheus
|
||||
|
||||
#define OCELOT_VERSION_MAJOR 3
|
||||
#define OCELOT_VERSION_MINOR 1
|
||||
#define OCELOT_VERSION_MINOR 0
|
||||
#define OCELOT_VERSION_BUGFIX 0
|
||||
#define OCELOT_VERSION_NREV 0
|
||||
#define OCELOT_VERSION "3.1.0"
|
||||
#define OCELOT_VERSION "3.0.0"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "peer.h"
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
typedef uint32_t torid_t;
|
||||
typedef xxh::hash_t<64> peerkey_t;
|
||||
typedef std::map<peerkey_t, peer> peer_list;
|
||||
typedef uint32_t userid_t;
|
||||
|
||||
class user;
|
||||
typedef std::shared_ptr<user> user_ptr;
|
||||
|
||||
typedef struct {
|
||||
int64_t uploaded;
|
||||
int64_t downloaded;
|
||||
int64_t corrupt;
|
||||
int64_t left;
|
||||
time_t last_announced;
|
||||
time_t first_announced;
|
||||
uint32_t announces;
|
||||
uint16_t port;
|
||||
bool visible;
|
||||
bool invalid_ip;
|
||||
user_ptr user;
|
||||
std::string ip_port;
|
||||
std::string ip;
|
||||
} peer;
|
||||
|
||||
typedef std::map<std::string, peer> peer_list;
|
||||
|
||||
enum freetype { NORMAL, FREE, NEUTRAL };
|
||||
|
||||
@@ -35,7 +53,7 @@ typedef struct {
|
||||
time_t last_flushed;
|
||||
peer_list seeders;
|
||||
peer_list leechers;
|
||||
peerkey_t last_selected_seeder;
|
||||
std::string last_selected_seeder;
|
||||
std::set<userid_t> tokened_users;
|
||||
} torrent;
|
||||
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
#ifndef SRC_PEER_H_
|
||||
#define SRC_PEER_H_
|
||||
|
||||
// Copyright [2017-2026] Orpheus
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "user.h"
|
||||
#include "xxhash.hpp"
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint32_t addr;
|
||||
uint16_t port;
|
||||
};
|
||||
char addr_port[sizeof(uint32_t) + sizeof(uint16_t)];
|
||||
} addr_port;
|
||||
|
||||
class peer {
|
||||
public:
|
||||
user_ptr user;
|
||||
addr_port ap;
|
||||
int64_t uploaded = 0;
|
||||
int64_t downloaded = 0;
|
||||
int64_t corrupt = 0;
|
||||
int64_t left = 0;
|
||||
time_t last_announced = 0;
|
||||
time_t first_announced = 0;
|
||||
uint32_t announces = 0;
|
||||
|
||||
peer(user_ptr u, addr_port ap) : user(u), ap(ap) {}
|
||||
};
|
||||
|
||||
#endif // SRC_PEER_H_
|
||||
+4
-37
@@ -6,7 +6,6 @@
|
||||
#include <sys/resource.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#include <spdlog/spdlog.h> // debug
|
||||
|
||||
@@ -23,6 +22,7 @@
|
||||
#include "jemalloc_parse.h"
|
||||
#include "report.h"
|
||||
#include "response.h"
|
||||
#include "user.h"
|
||||
|
||||
int jemalloc_fd;
|
||||
std::mutex jemalloc_fd_mutex;
|
||||
@@ -58,7 +58,7 @@ int dump_jemalloc(const char *filename, const char *opts) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string report_jemalloc_plain(const char *opts, const std::string &path) {
|
||||
std::string report_jemalloc_plain(const char *opts, const std::string path) {
|
||||
std::string filename(path + "/jemalloc.json." + std::to_string(reinterpret_cast<uintptr_t>(pthread_self())));
|
||||
if (dump_jemalloc(filename.c_str(), opts) != 0) {
|
||||
// return an empty string if something went bananas
|
||||
@@ -143,11 +143,11 @@ std::string report(const uint32_t announce_interval, const uint32_t announce_jit
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string report_prom_stats(const char *jemalloc_stats) {
|
||||
std::string report_prom_stats(const char *jemalloc) {
|
||||
std::ostringstream output;
|
||||
|
||||
struct ocelot_alloc_info ji;
|
||||
int result = jemalloc_parse(jemalloc_stats, &ji);
|
||||
int result = jemalloc_parse(jemalloc, &ji);
|
||||
|
||||
output << "ocelot_uptime " << time(NULL) - stats.start_time << "\n"
|
||||
"ocelot_version " << OCELOT_VERSION_MAJOR
|
||||
@@ -279,39 +279,6 @@ std::string report_prom_stats(const char *jemalloc_stats) {
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string report_torrent(const torrent &t) {
|
||||
std::string output = fmt::format(
|
||||
"{{\"id\":{},"
|
||||
"\"completed\":{},"
|
||||
"\"balance\":{},"
|
||||
"\"last_flushed\":{},"
|
||||
"\"last_selected_seeder\":\"{}\","
|
||||
"\"leech_type\":{},"
|
||||
"\"leecher_list\":[",
|
||||
t.id,
|
||||
t.completed,
|
||||
t.balance,
|
||||
t.last_flushed,
|
||||
t.last_selected_seeder,
|
||||
t.free_torrent
|
||||
);
|
||||
int n = 0;
|
||||
for (auto peer : t.leechers) {
|
||||
output += std::string(n++ == 0 ? "" : ",") + std::to_string(peer.second.user->get_id());
|
||||
}
|
||||
n = 0;
|
||||
output += "],\"seeder_list\":[";
|
||||
for (auto peer : t.seeders) {
|
||||
output += std::string(n++ == 0 ? "" : ",") + std::to_string(peer.second.user->get_id());
|
||||
}
|
||||
n = 0;
|
||||
output += "],\"fltoken_list\":[";
|
||||
for (userid_t id : t.tokened_users) {
|
||||
output += std::string(n++ == 0 ? "" : ",") + std::to_string(id);
|
||||
}
|
||||
return output + "]}\n";
|
||||
}
|
||||
|
||||
std::string report_user(const user_ptr u) {
|
||||
std::ostringstream output;
|
||||
output << "{\"id\":" << u->get_id()
|
||||
|
||||
+1
-4
@@ -12,14 +12,11 @@ void copy_stats(const struct stats_t *in, struct stats_t *out);
|
||||
std::string report(const uint32_t announce_interval, const uint32_t announce_jitter);
|
||||
|
||||
// a snapshot of jemalloc statistics
|
||||
std::string report_jemalloc_plain(const char *opts, const std::string &path);
|
||||
std::string report_jemalloc_plain(const char *opts, const std::string path);
|
||||
|
||||
// a prometheus scrape
|
||||
std::string report_prom_stats(const char *jemalloc_stats);
|
||||
|
||||
// torrent status report
|
||||
std::string report_torrent(const torrent &t);
|
||||
|
||||
// user report
|
||||
std::string report_user(const user_ptr u);
|
||||
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
@@ -10,13 +10,13 @@
|
||||
#include "misc_functions.h"
|
||||
#include "response.h"
|
||||
|
||||
const std::string http_head(size_t content_length, const client_opts_t &client_opts) {
|
||||
const std::string http_head(size_t content_length, client_opts_t &client_opts) {
|
||||
const std::string content_type = client_opts.html ? "text/html" : "text/plain";
|
||||
|
||||
// rely on preprocessor string concatenation
|
||||
std::string head = "HTTP/1.1 200 OK\r\nServer: Ocelot " OCELOT_VERSION "\r\n"
|
||||
"Content-Type: " + content_type + "\r\n"
|
||||
"Content-Length: " + std::to_string(content_length) + "\r\n";
|
||||
"Content-Length: " + inttostr(content_length) + "\r\n";
|
||||
if (client_opts.gzip) {
|
||||
head += "Content-Encoding: gzip\r\n";
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const std::string http_head(size_t content_length, const client_opts_t &client_o
|
||||
return head + "\r\n";
|
||||
}
|
||||
|
||||
const std::string http_response(const std::string &body, const client_opts_t &client_opts) {
|
||||
const std::string http_response(const std::string &body, client_opts_t &client_opts) {
|
||||
std::string out;
|
||||
bool processed = false;
|
||||
if (client_opts.html) {
|
||||
@@ -49,9 +49,9 @@ const std::string http_response(const std::string &body, const client_opts_t &cl
|
||||
return http_head(body.length(), client_opts) + body;
|
||||
}
|
||||
|
||||
const std::string error(const std::string &message, const client_opts_t &client_opts) {
|
||||
const std::string error(const std::string &message, client_opts_t &client_opts) {
|
||||
return http_response(
|
||||
"d14:failure reason" + std::to_string(message.length()) + ':' + message
|
||||
"d14:failure reason" + inttostr(message.length()) + ':' + message
|
||||
+ "12:min intervali5400e8:intervali5400ee",
|
||||
client_opts
|
||||
);
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
#ifndef SRC_RESPONSE_H_
|
||||
#define SRC_RESPONSE_H_
|
||||
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ocelot.h"
|
||||
|
||||
const std::string http_response(const std::string &body, const client_opts_t &client_opts);
|
||||
const std::string error(const std::string &message, const client_opts_t &client_opts);
|
||||
const std::string http_response(const std::string &body, client_opts_t &client_opts);
|
||||
const std::string error(const std::string &err, client_opts_t &client_opts);
|
||||
|
||||
#endif // SRC_RESPONSE_H_
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
site_comm::site_comm(config * conf) : logger(spdlog::get("logger")), t_active(false), verbose_flush(false) {
|
||||
site_comm::site_comm(config * conf) : t_active(false) {
|
||||
logger = spdlog::get("logger");
|
||||
load_config(conf);
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ void site_comm::reload_config(config * conf) {
|
||||
load_config(conf);
|
||||
}
|
||||
|
||||
bool site_comm::all_clear() const {
|
||||
bool site_comm::all_clear() {
|
||||
return (token_queue.size() == 0);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#ifndef SRC_SITE_COMM_H_
|
||||
#define SRC_SITE_COMM_H_
|
||||
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -35,7 +35,7 @@ class site_comm {
|
||||
bool verbose_flush;
|
||||
site_comm(config * conf);
|
||||
void reload_config(config * conf);
|
||||
bool all_clear() const;
|
||||
bool all_clear();
|
||||
void expire_token(int torrent, int user);
|
||||
void flush_tokens();
|
||||
~site_comm();
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include "user.h"
|
||||
|
||||
user::user(userid_t uid, bool leech, bool protect) : id(uid), deleted(false), leechstatus(leech), protect_ip(protect) {
|
||||
stats.leeching = 0;
|
||||
stats.seeding = 0;
|
||||
}
|
||||
+20
-30
@@ -1,48 +1,38 @@
|
||||
#ifndef SRC_USER_H_
|
||||
#define SRC_USER_H_
|
||||
|
||||
// Copyright [2017-2026] Orpheus
|
||||
// Copyright [2017-2024] Orpheus
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
typedef uint32_t userid_t;
|
||||
#include "ocelot.h"
|
||||
|
||||
class user {
|
||||
private:
|
||||
private:
|
||||
userid_t id;
|
||||
bool deleted;
|
||||
bool leechstatus;
|
||||
bool protect_ip;
|
||||
std::atomic<uint32_t> leeching;
|
||||
std::atomic<uint32_t> seeding;
|
||||
|
||||
public:
|
||||
user(userid_t uid, bool leech, bool protect) :
|
||||
id(uid),
|
||||
deleted(false),
|
||||
leechstatus(leech),
|
||||
protect_ip(protect),
|
||||
leeching(0),
|
||||
seeding(0)
|
||||
{}
|
||||
|
||||
userid_t get_id() const { return id; }
|
||||
bool is_deleted() const { return deleted; }
|
||||
bool is_protected() const { return protect_ip; }
|
||||
bool can_leech() const { return leechstatus; }
|
||||
uint32_t get_leeching() const { return leeching; }
|
||||
uint32_t get_seeding() const { return seeding; }
|
||||
struct {
|
||||
std::atomic<uint32_t> leeching;
|
||||
std::atomic<uint32_t> seeding;
|
||||
} stats;
|
||||
|
||||
public:
|
||||
user(userid_t uid, bool leech, bool protect);
|
||||
userid_t get_id() { return id; }
|
||||
bool is_deleted() { return deleted; }
|
||||
void set_deleted(bool status) { deleted = status; }
|
||||
void set_leechstatus(bool status) { leechstatus = status; }
|
||||
bool is_protected() { return protect_ip; }
|
||||
void set_protected(bool status) { protect_ip = status; }
|
||||
void decr_leeching() { --leeching; }
|
||||
void decr_seeding() { --seeding; }
|
||||
void incr_leeching() { ++leeching; }
|
||||
void incr_seeding() { ++seeding; }
|
||||
bool can_leech() { return leechstatus; }
|
||||
void set_leechstatus(bool status) { leechstatus = status; }
|
||||
void decr_leeching() { --stats.leeching; }
|
||||
void decr_seeding() { --stats.seeding; }
|
||||
void incr_leeching() { ++stats.leeching; }
|
||||
void incr_seeding() { ++stats.seeding; }
|
||||
uint32_t get_leeching() { return stats.leeching; }
|
||||
uint32_t get_seeding() { return stats.seeding; }
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<user> user_ptr;
|
||||
|
||||
#endif // SRC_USER_H_
|
||||
+350
-389
File diff suppressed because it is too large.
Load diff
+17
-35
@@ -5,33 +5,22 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <ctime>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "site_comm.h"
|
||||
|
||||
enum tracker_status { OPEN, PAUSED, CLOSING }; // tracker status
|
||||
|
||||
typedef struct {
|
||||
std::string useragent;
|
||||
int64_t uploaded;
|
||||
int64_t downloaded;
|
||||
int64_t left;
|
||||
int64_t corrupt;
|
||||
bool event_completed;
|
||||
bool event_started;
|
||||
bool event_stopped;
|
||||
} announce_context;
|
||||
|
||||
class worker {
|
||||
private:
|
||||
private:
|
||||
config * conf;
|
||||
mysql * db;
|
||||
site_comm * s_comm;
|
||||
@@ -47,10 +36,10 @@ class worker {
|
||||
std::uniform_int_distribution<int> jitter;
|
||||
static std::mutex client_len_mutex;
|
||||
|
||||
uint32_t announce_interval;
|
||||
uint32_t del_reason_lifetime;
|
||||
uint32_t peers_timeout;
|
||||
uint32_t numwant_limit;
|
||||
unsigned int announce_interval;
|
||||
unsigned int del_reason_lifetime;
|
||||
unsigned int peers_timeout;
|
||||
unsigned int numwant_limit;
|
||||
bool keepalive_enabled;
|
||||
std::string site_password;
|
||||
std::string report_password;
|
||||
@@ -60,29 +49,22 @@ class worker {
|
||||
void do_start_reaper();
|
||||
void reap_peers();
|
||||
void reap_del_reasons();
|
||||
peer_list::iterator add_peer(peer_list &peer_list, const peerkey_t &peer_key);
|
||||
|
||||
static std::string get_del_reason(int code);
|
||||
|
||||
static peer_list::iterator add_leecher(torrent &tor, user_ptr &u, const addr_port &ap, const peerkey_t &peer_key);
|
||||
static peer_list::iterator add_seeder(torrent &tor, user_ptr &u, const addr_port &ap, const peerkey_t &peer_key);
|
||||
static peer_list::iterator move_seeder_to_leecher(torrent &tor, user_ptr &u, peer_list::iterator &peer_it, const peerkey_t &peer_key);
|
||||
static peer_list::iterator move_leecher_to_seeder(torrent &tor, user_ptr &u, peer_list::iterator &peer_it, const peerkey_t &peer_key);
|
||||
static peer* insert_seeder(torrent &tor, user_ptr &u, peer_list::iterator &peer_it, const peerkey_t &peer_key);
|
||||
static void remove_seeder(torrent &tor, user_ptr &u, peer_list::iterator &peer_it);
|
||||
std::string get_del_reason(int code);
|
||||
peer_list::iterator add_peer(peer_list &peer_list, const std::string &peer_id);
|
||||
inline bool peer_is_visible(user_ptr &u, peer *p);
|
||||
|
||||
public:
|
||||
worker(config * conf_obj, torrent_list &torrents, user_list &users, std::vector<std::string> &_whitelist, mysql * db_obj, site_comm * sc);
|
||||
void reload_config(config * conf);
|
||||
std::string work(const std::string &input, uint32_t remote_addr, client_opts_t &client_opts);
|
||||
std::string announce(const std::string &peer_id, torrent &tor, user_ptr &u, const announce_context *ctx, uint32_t numwant, const addr_port &ap, const client_opts_t &client_opts);
|
||||
std::string work(const std::string &input, std::string &ip, client_opts_t &client_opts);
|
||||
std::string announce(const std::string &input, const std::string &peer_id, torrent &tor, user_ptr &u, params_type ¶ms, params_type &headers, std::string &ip, client_opts_t &client_opts);
|
||||
std::string scrape(const std::list<std::string> &infohashes, params_type &headers, client_opts_t &client_opts);
|
||||
std::string update(params_type ¶ms, const client_opts_t &client_opts);
|
||||
std::string update(params_type ¶ms, client_opts_t &client_opts);
|
||||
|
||||
void reload_lists();
|
||||
bool shutdown();
|
||||
|
||||
tracker_status get_status() const { return status; }
|
||||
tracker_status get_status() { return status; }
|
||||
|
||||
void start_reaper();
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user