mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-10 20:22:31 +00:00
Add optional Lua scripting capabilties - kannibalox@gmail.com
This commit is contained in:
@@ -107,6 +107,8 @@ libsub_root_a_SOURCES = \
|
||||
rpc/exec_file.h \
|
||||
rpc/fixed_key.h \
|
||||
rpc/ip_table_list.h \
|
||||
rpc/lua.h \
|
||||
rpc/lua.cc \
|
||||
rpc/object_storage.cc \
|
||||
rpc/object_storage.h \
|
||||
rpc/parse.cc \
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include "utils/file_status_cache.h"
|
||||
|
||||
#include "globals.h"
|
||||
#include "rpc/lua.h"
|
||||
#include "control.h"
|
||||
#include "command_helpers.h"
|
||||
|
||||
@@ -232,6 +233,7 @@ void
|
||||
initialize_command_local() {
|
||||
core::DownloadList* dList = control->core()->download_list();
|
||||
core::DownloadStore* dStore = control->core()->download_store();
|
||||
rpc::LuaEngine* luaEngine = control->lua_engine();
|
||||
torrent::ChunkManager* chunkManager = torrent::chunk_manager();
|
||||
torrent::FileManager* fileManager = torrent::file_manager();
|
||||
|
||||
@@ -315,6 +317,11 @@ initialize_command_local() {
|
||||
|
||||
CMD2_ANY_V ("session.save", std::bind(&core::DownloadList::session_save, dList));
|
||||
|
||||
#ifdef HAVE_LUA
|
||||
CMD2_ANY ("lua.execute", std::bind(&rpc::execute_lua, luaEngine, std::placeholders::_1, std::placeholders::_2, 0));
|
||||
CMD2_ANY ("lua.execute.str", std::bind(&rpc::execute_lua, luaEngine, std::placeholders::_1, std::placeholders::_2, rpc::LuaEngine::flag_string));
|
||||
#endif
|
||||
|
||||
#define CMD2_EXECUTE(key, flags) \
|
||||
CMD2_ANY(key, std::bind(&rpc::ExecFile::execute_object, &rpc::execFile, std::placeholders::_2, flags));
|
||||
|
||||
|
||||
+3
-36
@@ -1,39 +1,3 @@
|
||||
// rTorrent - BitTorrent client
|
||||
// Copyright (C) 2005-2011, Jari Sundell
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations
|
||||
// including the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for
|
||||
// all of the code used other than OpenSSL. If you modify file(s)
|
||||
// with this exception, you may extend this exception to your version
|
||||
// of the file(s), but you are not obligated to do so. If you do not
|
||||
// wish to do so, delete this exception statement from your version.
|
||||
// If you delete this exception statement from all source files in the
|
||||
// program, then also delete it here.
|
||||
//
|
||||
// Contact: Jari Sundell <jaris@ifi.uio.no>
|
||||
//
|
||||
// Skomakerveien 33
|
||||
// 3185 Skoppum, NORWAY
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
@@ -53,6 +17,7 @@
|
||||
#include "input/manager.h"
|
||||
#include "input/input_event.h"
|
||||
#include "rpc/command_scheduler.h"
|
||||
#include "rpc/lua.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "rpc/scgi.h"
|
||||
#include "rpc/object_storage.h"
|
||||
@@ -68,6 +33,7 @@ Control::Control() :
|
||||
|
||||
m_commandScheduler(new rpc::CommandScheduler()),
|
||||
m_objectStorage(new rpc::object_storage()),
|
||||
m_lua_engine(new rpc::LuaEngine()),
|
||||
m_directory_events(new torrent::directory_events()),
|
||||
|
||||
m_tick(0),
|
||||
@@ -99,6 +65,7 @@ Control::~Control() {
|
||||
delete m_directory_events;
|
||||
delete m_commandScheduler;
|
||||
delete m_objectStorage;
|
||||
delete m_lua_engine;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace rpc {
|
||||
class CommandScheduler;
|
||||
class XmlRpc;
|
||||
class object_storage;
|
||||
class LuaEngine;
|
||||
}
|
||||
|
||||
namespace torrent {
|
||||
@@ -102,6 +103,7 @@ public:
|
||||
|
||||
rpc::CommandScheduler* command_scheduler() { return m_commandScheduler; }
|
||||
rpc::object_storage* object_storage() { return m_objectStorage; }
|
||||
rpc::LuaEngine* lua_engine() { return m_lua_engine; }
|
||||
|
||||
torrent::directory_events* directory_events() { return m_directory_events; }
|
||||
|
||||
@@ -126,6 +128,7 @@ private:
|
||||
|
||||
rpc::CommandScheduler* m_commandScheduler;
|
||||
rpc::object_storage* m_objectStorage;
|
||||
rpc::LuaEngine* m_lua_engine;
|
||||
torrent::directory_events* m_directory_events;
|
||||
|
||||
uint64_t m_tick;
|
||||
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "rpc/lua.h"
|
||||
#ifdef HAVE_LUA
|
||||
#include <lua.hpp>
|
||||
#endif
|
||||
|
||||
#include <rak/error_number.h>
|
||||
#include <rak/path.h>
|
||||
#include <rak/string_manip.h>
|
||||
#include <torrent/object.h>
|
||||
|
||||
#include "core/download.h"
|
||||
#include "rpc/command.h"
|
||||
#include "rpc/command_map.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "rpc/xmlrpc.h"
|
||||
|
||||
namespace rpc {
|
||||
|
||||
const int LuaEngine::flag_string;
|
||||
const std::string LuaEngine::module_name = "rtorrent";
|
||||
|
||||
#ifdef HAVE_LUA
|
||||
|
||||
LuaEngine::LuaEngine() {
|
||||
m_luaState = luaL_newstate();
|
||||
luaL_openlibs(m_luaState);
|
||||
set_package_preload();
|
||||
}
|
||||
|
||||
LuaEngine::~LuaEngine() { lua_close(m_luaState); }
|
||||
|
||||
void
|
||||
LuaEngine::set_package_preload() {
|
||||
auto l_state = m_luaState;
|
||||
lua_getglobal(l_state, "package");
|
||||
lua_getfield(l_state, -1, "preload");
|
||||
lua_pushcfunction(l_state, LuaEngine::lua_init_module);
|
||||
lua_setfield(l_state, -2, LuaEngine::module_name.c_str());
|
||||
lua_pop(l_state, 2);
|
||||
}
|
||||
|
||||
void
|
||||
check_lua_status(lua_State* l_state, int status) {
|
||||
if (status != LUA_OK) {
|
||||
throw torrent::input_error(lua_tostring(l_state, -1));
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
LuaEngine::search_lua_path(lua_State* l_state) {
|
||||
// Get package.path
|
||||
lua_getglobal(l_state, "package");
|
||||
lua_getfield(l_state, -1, "path");
|
||||
std::string lua_path(lua_tostring(l_state, -1));
|
||||
lua_pop(l_state, 2);
|
||||
|
||||
// Use lambda here to ensure file closes as soon as it goes out of scope
|
||||
auto file_exists = [](const std::string& path) -> bool {
|
||||
std::ifstream file(path);
|
||||
return file.is_open();
|
||||
};
|
||||
|
||||
// Tokenize path using ';' as delimiter
|
||||
size_t pos = 0, end;
|
||||
while ((end = lua_path.find(';', pos)) != std::string::npos) {
|
||||
std::string file_path(lua_path.substr(pos, end - pos));
|
||||
size_t ppos = 0;
|
||||
while ((ppos = file_path.find('?')) != std::string::npos) {
|
||||
file_path.replace(ppos, 1, LuaEngine::module_name);
|
||||
}
|
||||
if (file_exists(file_path)) {
|
||||
return file_path;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int
|
||||
LuaEngine::lua_rtorrent_call(lua_State* l_state) {
|
||||
auto method = lua_tostring(l_state, 1);
|
||||
lua_remove(l_state, 1);
|
||||
torrent::Object object;
|
||||
rpc::target_type target = rpc::make_target();
|
||||
;
|
||||
rpc::CommandMap::iterator itr = rpc::commands.find(std::string(method).c_str());
|
||||
if (itr == rpc::commands.end()) {
|
||||
throw torrent::input_error("method not found: " + std::string(method));
|
||||
}
|
||||
object = lua_callstack_to_object(l_state, itr->second.m_flags, &target);
|
||||
try {
|
||||
const auto& result = rpc::commands.call_command(itr, object, target);
|
||||
object_to_lua(l_state, result);
|
||||
return 1;
|
||||
} catch (torrent::base_error& e) {
|
||||
throw luaL_error(l_state, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
LuaEngine::lua_init_module(lua_State* l_state) {
|
||||
// Should this throw if it fails to find the file?
|
||||
auto lua_file = search_lua_path(l_state);
|
||||
if (!lua_file.empty()) {
|
||||
check_lua_status(l_state, luaL_loadfile(l_state, lua_file.c_str()));
|
||||
}
|
||||
lua_createtable(l_state, 0, 1);
|
||||
// Assign functions
|
||||
lua_pushliteral(l_state, "call");
|
||||
lua_pushcfunction(l_state, LuaEngine::lua_rtorrent_call);
|
||||
lua_settable(l_state, -3);
|
||||
if (!lua_file.empty()) {
|
||||
check_lua_status(l_state, lua_pcall(l_state, 1, 1, 0));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target) {
|
||||
if (!obj.is_string()) {
|
||||
throw torrent::input_error("invalid parameters: target must be a string");
|
||||
}
|
||||
std::string target_string = obj.as_string();
|
||||
bool require_index = (call_flags & (CommandMap::flag_tracker_target | CommandMap::flag_file_target));
|
||||
if (target_string.empty() && !require_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Length of SHA1 hash is 40
|
||||
if (target_string.size() < 40) {
|
||||
throw torrent::input_error("invalid parameters: invalid target");
|
||||
}
|
||||
|
||||
char type = 'd';
|
||||
std::string hash;
|
||||
std::string index;
|
||||
const auto& delim_pos = target_string.find_first_of(':', 40);
|
||||
if (delim_pos == target_string.npos || delim_pos + 2 >= target_string.size()) {
|
||||
if (require_index) {
|
||||
throw torrent::input_error("invalid parameters: no index");
|
||||
}
|
||||
hash = target_string;
|
||||
} else {
|
||||
hash = target_string.substr(0, delim_pos);
|
||||
type = target_string[delim_pos + 1];
|
||||
index = target_string.substr(delim_pos + 2);
|
||||
}
|
||||
core::Download* download = xmlrpc.slot_find_download()(hash.c_str());
|
||||
|
||||
if (download == nullptr)
|
||||
throw torrent::input_error("invalid parameters: info-hash not found");
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'd':
|
||||
*target = rpc::make_target(download);
|
||||
break;
|
||||
case 'f':
|
||||
*target = rpc::make_target(command_base::target_file, xmlrpc.slot_find_file()(download, std::stoi(std::string(index))));
|
||||
break;
|
||||
case 't':
|
||||
*target =
|
||||
rpc::make_target(command_base::target_tracker, xmlrpc.slot_find_tracker()(download, std::stoi(std::string(index))));
|
||||
break;
|
||||
case 'p': {
|
||||
if (index.size() < 40) {
|
||||
throw torrent::input_error("Not a hash string.");
|
||||
}
|
||||
torrent::HashString hash;
|
||||
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
||||
*target = rpc::make_target(command_base::target_peer, xmlrpc.slot_find_peer()(download, hash));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw torrent::input_error("invalid parameters: unexpected target type");
|
||||
}
|
||||
} catch (const std::logic_error&) {
|
||||
throw torrent::input_error("invalid parameters: invalid index");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
object_to_lua(lua_State* l_state, torrent::Object const& object) {
|
||||
// Converts an object to a single Lua stack object
|
||||
switch (object.type()) {
|
||||
case torrent::Object::TYPE_VALUE:
|
||||
lua_pushnumber(l_state, static_cast<double>(object.as_value()));
|
||||
break;
|
||||
case torrent::Object::TYPE_NONE:
|
||||
lua_pushnil(l_state);
|
||||
break;
|
||||
case torrent::Object::TYPE_STRING:
|
||||
lua_pushstring(l_state, object.as_string().c_str());
|
||||
break;
|
||||
case torrent::Object::TYPE_LIST: {
|
||||
int index = 1;
|
||||
auto object_list = object.as_list();
|
||||
|
||||
lua_createtable(l_state, static_cast<int>(object_list.size()), 0);
|
||||
int table_idx = lua_gettop(l_state);
|
||||
for (const auto& itr : object_list) {
|
||||
object_to_lua(l_state, itr);
|
||||
lua_rawseti(l_state, table_idx, lua_Integer(index++));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case torrent::Object::TYPE_MAP: {
|
||||
auto object_map = object.as_map();
|
||||
lua_createtable(l_state, 0, static_cast<int>(object_map.size()));
|
||||
int table_idx = lua_gettop(l_state);
|
||||
for (const auto& itr : object_map) {
|
||||
object_to_lua(l_state, itr.second);
|
||||
lua_pushlstring(l_state, itr.first.c_str(), itr.first.size());
|
||||
lua_rawset(l_state, table_idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
lua_pushnumber(l_state, lua_Number(object.type()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
lua_to_object(lua_State* l_state) {
|
||||
switch (lua_type(l_state, -1)) {
|
||||
case LUA_TNUMBER:
|
||||
return torrent::Object(lua_tonumber(l_state, -1));
|
||||
case LUA_TSTRING:
|
||||
return torrent::Object(lua_tostring(l_state, -1));
|
||||
case LUA_TBOOLEAN:
|
||||
return torrent::Object(lua_toboolean(l_state, -1));
|
||||
case LUA_TNIL:
|
||||
return torrent::Object((torrent::Object::value_type)0);
|
||||
case LUA_TTABLE: {
|
||||
lua_pushnil(l_state);
|
||||
int status = lua_next(l_state, -2);
|
||||
if (status == 0)
|
||||
return torrent::Object::create_map();
|
||||
// If the table starts at 1, assume it's an array
|
||||
if (lua_isnumber(l_state, -2) && (lua_tonumber(l_state, -2) == 1)) {
|
||||
torrent::Object list = torrent::Object::create_list();
|
||||
list.insert_back(lua_to_object(l_state));
|
||||
lua_pop(l_state, 1);
|
||||
while (lua_next(l_state, -2) != 0) {
|
||||
list.insert_back(lua_to_object(l_state));
|
||||
lua_pop(l_state, 1);
|
||||
}
|
||||
return list;
|
||||
} else {
|
||||
torrent::Object map = torrent::Object::create_map();
|
||||
// Reset the stack and start from the first element
|
||||
lua_pop(l_state, 2);
|
||||
lua_pushnil(l_state);
|
||||
while (lua_next(l_state, -2) != 0) {
|
||||
std::string key;
|
||||
switch (lua_type(l_state, -2)) {
|
||||
case LUA_TSTRING:
|
||||
key = lua_tostring(l_state, -2);
|
||||
break;
|
||||
default:
|
||||
// Bencode dictionaries require string keys, but naively
|
||||
// calling lua_tostring will auto-convert any other object
|
||||
// (particularly numbers) to strings in-place, which would
|
||||
// throw off lua_next, so we copy the value and only then
|
||||
// get the string.
|
||||
// https://www.lua.org/manual/5.3/manual.html#lua_tolstring
|
||||
lua_pushvalue(l_state, -2);
|
||||
key = lua_tostring(l_state, -1);
|
||||
lua_pop(l_state, 1);
|
||||
break;
|
||||
}
|
||||
map.insert_key(key, lua_to_object(l_state));
|
||||
lua_pop(l_state, 1);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
return torrent::Object(lua_tostring(l_state, -1));
|
||||
}
|
||||
default:
|
||||
std::string result = lua_tostring(l_state, -1);
|
||||
lua_pop(l_state, 1);
|
||||
return torrent::Object(result);
|
||||
}
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
lua_callstack_to_object(lua_State* l_state, int command_flags, rpc::target_type* target) {
|
||||
torrent::Object object;
|
||||
if (lua_gettop(l_state) == 0) {
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
if (!lua_isstring(l_state, 1)) {
|
||||
throw torrent::input_error("invalid parameters: target must be a string");
|
||||
}
|
||||
object_to_target(torrent::Object(lua_tostring(l_state, 1)), command_flags, target);
|
||||
|
||||
// start from the second argument since the first is the target
|
||||
lua_remove(l_state, 1);
|
||||
|
||||
if (lua_gettop(l_state) == 0) {
|
||||
return torrent::Object();
|
||||
} else {
|
||||
torrent::Object result = torrent::Object::create_list();
|
||||
torrent::Object::list_type& list_ref = result.as_list();
|
||||
while (lua_gettop(l_state) != 0) {
|
||||
list_ref.insert(list_ref.begin(), lua_to_object(l_state));
|
||||
lua_remove(l_state, -1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
int
|
||||
lua_rtorrent_call(lua_State* l_state) {
|
||||
torrent::Object object;
|
||||
rpc::target_type target = rpc::make_target();
|
||||
auto method = lua_tostring(l_state, 1);
|
||||
lua_remove(l_state, 1);
|
||||
|
||||
rpc::CommandMap::iterator itr = rpc::commands.find(method);
|
||||
if (itr == rpc::commands.end()) {
|
||||
throw torrent::input_error("method not found: " + std::string(method));
|
||||
}
|
||||
object = lua_callstack_to_object(l_state, itr->second.m_flags, &target);
|
||||
try {
|
||||
const auto& result = rpc::commands.call_command(itr, object, target);
|
||||
object_to_lua(l_state, result);
|
||||
return 1;
|
||||
} catch (torrent::base_error& e) {
|
||||
throw luaL_error(l_state, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
execute_lua(LuaEngine* engine, rpc::target_type target_type, torrent::Object const& raw_args, int flags) {
|
||||
size_t lua_argc = 1; // Target is always present, even if empty
|
||||
lua_State* l_state = engine->state();
|
||||
std::string target_string;
|
||||
|
||||
switch (target_type.first) {
|
||||
case (command_base::target_download):
|
||||
core::Download* dl_target = (core::Download*)target_type.second;
|
||||
torrent::HashString infohash = dl_target->info()->hash();
|
||||
target_string = rak::transform_hex_str(infohash);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (raw_args.type()) {
|
||||
case torrent::Object::TYPE_LIST: {
|
||||
const torrent::Object::list_type& args = raw_args.as_list();
|
||||
if (flags & LuaEngine::flag_string) {
|
||||
check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str()));
|
||||
} else {
|
||||
check_lua_status(l_state, luaL_loadfile(l_state, args.begin()->as_string().c_str()));
|
||||
}
|
||||
object_to_lua(l_state, target_string);
|
||||
for (torrent::Object::list_const_iterator itr = std::next(args.begin()), last = args.end(); itr != last; itr++) {
|
||||
object_to_lua(l_state, *itr);
|
||||
}
|
||||
lua_argc += args.size() - 1;
|
||||
break;
|
||||
}
|
||||
case torrent::Object::TYPE_STRING: {
|
||||
const torrent::Object::string_type& target = raw_args.as_string();
|
||||
if (flags & LuaEngine::flag_string) {
|
||||
check_lua_status(l_state, luaL_loadstring(l_state, target.c_str()));
|
||||
} else {
|
||||
check_lua_status(l_state, luaL_loadfile(l_state, target.c_str()));
|
||||
}
|
||||
object_to_lua(l_state, target_string);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw torrent::input_error("execute_lua(...): cannot call lua with arg type " + std::to_string(raw_args.type()));
|
||||
}
|
||||
check_lua_status(l_state, lua_pcall(l_state, lua_argc, LUA_MULTRET, 0));
|
||||
return lua_to_object(l_state);
|
||||
}
|
||||
|
||||
#else
|
||||
torrent::Object
|
||||
execute_lua(LuaEngine* engine, torrent::Object const& rawArgs, int flags) {
|
||||
throw torrent::input_error("Lua support not enabled");
|
||||
return torrent::Object();
|
||||
}
|
||||
LuaEngine::LuaEngine() {}
|
||||
LuaEngine::~LuaEngine() {}
|
||||
#endif
|
||||
} // namespace rpc
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef RTORRENT_LUA_H
|
||||
#define RTORRENT_LUA_H
|
||||
|
||||
#include "rpc/command.h"
|
||||
#include <torrent/object.h>
|
||||
|
||||
#ifdef HAVE_LUA
|
||||
#include <lua.hpp>
|
||||
#endif
|
||||
|
||||
namespace rpc {
|
||||
|
||||
class LuaEngine {
|
||||
public:
|
||||
static const int flag_string = 0x1;
|
||||
static const std::string module_name;
|
||||
|
||||
LuaEngine();
|
||||
~LuaEngine();
|
||||
#ifdef HAVE_LUA
|
||||
// lua_CFunctions
|
||||
static int lua_init_module(lua_State* l_state);
|
||||
static int lua_rtorrent_call(lua_State* l_state);
|
||||
void set_package_preload();
|
||||
lua_State* state() { return m_luaState; }
|
||||
|
||||
private:
|
||||
static std::string search_lua_path(lua_State* l_state);
|
||||
lua_State* m_luaState;
|
||||
#endif
|
||||
};
|
||||
torrent::Object execute_lua(LuaEngine* engine, rpc::target_type target, const torrent::Object& raw_args, int flags);
|
||||
|
||||
#ifdef HAVE_LUA
|
||||
int rtorrent_call(lua_State* l_state);
|
||||
void init_rtorrent_module(lua_State* l_state);
|
||||
void object_to_lua(lua_State* l_state, torrent::Object const& object);
|
||||
void check_lua_status(lua_State* l_state, int status);
|
||||
torrent::Object lua_to_object(lua_State* l_state);
|
||||
torrent::Object lua_callstack_to_object(lua_State* l_state, int command_flags, rpc::target_type* target);
|
||||
#endif
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user