1
0
Fork 0

Merge pull request #3232 from cuberite/LuaCallback

Lua callback
This commit is contained in:
Mattes D 2016-06-30 10:42:58 +02:00 committed by GitHub
commit 9af664dc73
30 changed files with 1631 additions and 2299 deletions

View File

@ -2,7 +2,7 @@ return
{
cPlugin =
{
Desc = [[cPlugin describes a Lua plugin. This page is dedicated to new-style plugins and contain their functions. Each plugin has its own Plugin object.
Desc = [[cPlugin describes a Lua plugin. This page is dedicated to new-style plugins and contain their functions. Each plugin has its own cPlugin object.
]],
Functions =
{
@ -22,10 +22,10 @@ return
cPluginLua =
{
Desc = "",
Desc = "(<b>OBSOLETE</b>) This class is no longer useful in the API and will be removed as soon as all core plugins are migrated away from it. The {{cPlugin}} class serves as the main plugin instance's interface.",
Functions =
{
AddWebTab = { Params = "", Return = "", Notes = "Adds a new webadmin tab" },
AddWebTab = { Params = "Title, HandlerFn", Return = "", Notes = "<b>OBSOLETE</b> - Use {{cWebAdmin}}:AddWebTab() instead." },
},
Inherits = "cPlugin",
}, -- cPluginLua
@ -81,6 +81,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
GetNumLoadedPlugins = { Params = "", Return = "number", Notes = "Returns the number of loaded plugins (psLoaded only)" },
GetNumPlugins = { Params = "", Return = "number", Notes = "Returns the number of plugins, including the disabled, errored, unloaded and not-found ones" },
GetPlugin = { Params = "PluginName", Return = "{{cPlugin}}", Notes = "(<b>DEPRECATED, UNSAFE</b>) Returns a plugin handle of the specified plugin, or nil if such plugin is not loaded. Note thatdue to multithreading the handle is not guaranteed to be safe for use when stored - a single-plugin reload may have been triggered in the mean time for the requested plugin." },
GetPluginFolderName = { Params = "PluginName", Return = "string", Notes = "Returns the name of the folder from which the plugin was loaded (without the \"Plugins\" part). Used as a plugin's display name." },
GetPluginsPath = { Params = "", Return = "string", Notes = "Returns the path where the individual plugin folders are located. Doesn't include the path separator at the end of the returned string." },
IsCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if in-game Command is already bound (by any plugin)" },
IsConsoleCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if console Command is already bound (by any plugin)" },

View File

@ -5,7 +5,15 @@ return
Desc = "",
Functions =
{
AddWebTab = { Params = "Title, UrlPath, HandlerFn", Return = "", Notes = "(STATIC) Adds a new web tab to webadmin. The tab uses \"Title\" as its display string and is identified in the URL using the UrlPath (https://server.domain.com/webadmin/{PluginName}/{UrlPath}). The HandlerFn is the callback function that is called when the admin accesses the page, it has the following signature:<br/><pre class=\"prettyprint lang-lua\">function ({{a_Request|HTTPRequest}}, a_UrlPath)<br/> return Content, ContentType<br/>end</pre> URLPath must not contain a '/', the recommendation is to use only 7-bit-clean ASCII character set." },
GetAllWebTabs = { Params = "", Return = "array-table", Notes = "(STATIC) Returns an array-table with each item describing a web tab, for all web tabs registered in the WebAdmin, for all plugins. The returned table has the following format:<br/><pre class=\"prettyprint lang-lua\">{<br/> {<br/> PluginName = \"Plugin's API name\",<br/> UrlPath = \"UrlPath given to AddWebTab\",<br/> Title = \"Title given to AddWebTab\",<br/> },<br/> ...<br/>}"},
GetBaseURL = { Params = "URL", Return = "string", Notes = "(STATIC) Returns the string that is the path of the base webadmin (\"../../../webadmin\") relative to the given URL." },
GetContentTypeFromFileExt = { Params = "FileExt", Return = "string", Notes = "(STATIC) Returns the content-type that should be used for files with the specified extension (without the dot), such as \"text/plain\" for the \"txt\" extension. If the extension is not known, returns an empty string." },
GetHTMLEscapedString = { Params = "string", Return = "string", Notes = "(STATIC) Gets the HTML-escaped representation of a requested string. This is useful for user input and game data that is not guaranteed to be escaped already." },
GetPage = { Params = "{{Request|HTTPRequest}}", Return = "table", Notes = "(STATIC) Returns the (inner HTML) page contents for the specified request. Calls the appropriate WebTab handler registered via AddWebTab() and returns the information from that plugin wrapped in a table with the following structure:<br/><pre class=\"prettyprint lang-lua\">{<br/> Content = \"\", -- Content returned by the plugin<br/> ContentType = \"\", -- Content type returned by the plugin, or \"text/html\" if none returned<br/> UrlPath = \"\", -- UrlPath decoded from the request<br/> TabTitle = \"\", -- Title of the tab that handled the request, as given to AddWebTab()<br/> PluginName = \"\", -- API name of the plugin that handled the request<br/> PluginFolder = \"\", -- Folder name (= display name) of the plugin that handled the request<br/>}</pre>This function is mainly used in the webadmin template file." },
GetPorts = { Params = "", Return = "string", Notes = "Returns a comma-separated list of ports on which the webadmin is configured to listen. Note that this list does include ports that may currently be unavailable (another server was already listening on them prior to launching Cuberite)." },
GetURLEncodedString = { Params = "string", Return = "string", Notes = "(STATIC) Returns the string given to it escaped by URL encoding, which makes the string suitable for transmission in an URL. Invalid characters are turned into \"%xy\" values." },
Reload = { Params = "", Return = "", Notes = "Reloads the webadmin's config - the allowed logins, the template script and the login page. Note that reloading will not change the \"enabled\" state of the server, and it will not update listening ports. Existing WebTabs will be kept registered even after the reload." },
},
}, -- cWebAdmin

View File

@ -1876,8 +1876,8 @@ end
function HandleConsoleSchedule(a_Split)
local prev = os.clock()
LOG("Scheduling a task for 2 seconds in the future (current os.clock is " .. prev .. ")")
cRoot:Get():GetDefaultWorld():ScheduleTask(40,
LOG("Scheduling a task for 5 seconds in the future (current os.clock is " .. prev .. ")")
cRoot:Get():GetDefaultWorld():ScheduleTask(5 * 20,
function ()
local current = os.clock()
local diff = current - prev

View File

@ -20,7 +20,7 @@ end
function GetDefaultPage()
local function GetDefaultPage()
local PM = cRoot:Get():GetPluginManager()
local SubTitle = "Current Game"
@ -55,30 +55,31 @@ end
function ShowPage(WebAdmin, TemplateRequest)
SiteContent = {}
local BaseURL = WebAdmin:GetBaseURL(TemplateRequest.Request.Path)
local BaseURL = cWebAdmin:GetBaseURL(TemplateRequest.Request.Path)
local Title = "Cuberite WebAdmin"
local NumPlayers = cRoot:Get():GetServer():GetNumPlayers()
local MemoryUsageKiB = cRoot:GetPhysicalRAMUsage()
local NumChunks = cRoot:Get():GetTotalChunkCount()
local PluginPage = WebAdmin:GetPage(TemplateRequest.Request)
local PluginPage = cWebAdmin:GetPage(TemplateRequest.Request)
local PageContent = PluginPage.Content
local SubTitle = PluginPage.PluginName
if (PluginPage.TabName ~= "") then
SubTitle = PluginPage.PluginName .. " - " .. PluginPage.TabName
local SubTitle = PluginPage.PluginFolder
if (PluginPage.UrlPath ~= "") then
SubTitle = PluginPage.PluginFolder .. " - " .. PluginPage.TabTitle
end
if (PageContent == "") then
PageContent, SubTitle = GetDefaultPage()
end
--[[
-- 2016-01-15 Mattes: This wasn't used anywhere in the code, no idea what it was supposed to do
local reqParamsClass = ""
for key,value in pairs(TemplateRequest.Request.Params) do
for key, value in pairs(TemplateRequest.Request.Params) do
reqParamsClass = reqParamsClass .. " param-" .. string.lower(string.gsub(key, "[^a-zA-Z0-9]+", "-") .. "-" .. string.gsub(value, "[^a-zA-Z0-9]+", "-"))
end
if (string.gsub(reqParamsClass, "%s", "") == "") then
reqParamsClass = " no-param"
end
--]]
Output([[
<!-- Copyright Justin S and Cuberite Team, licensed under CC-BY-SA 3.0 -->
@ -133,20 +134,39 @@ function ShowPage(WebAdmin, TemplateRequest)
<td class="trow1 smalltext">
]])
local AllPlugins = WebAdmin:GetPlugins()
for key,value in pairs(AllPlugins) do
local PluginWebTitle = value:GetWebTitle()
local TabNames = value:GetTabNames()
if (GetTableSize(TabNames) > 0) then
Output("<div><a class='usercp_nav_item usercp_nav_pmfolder' style='text-decoration:none;'><b>"..PluginWebTitle.."</b></a></div>\n");
for webname,prettyname in pairs(TabNames) do
Output("<div><a href='" .. BaseURL .. PluginWebTitle .. "/" .. webname .. "' class='usercp_nav_item usercp_nav_sub_pmfolder'>" .. prettyname .. "</a></div>\n")
-- Get all tabs:
local perPluginTabs = {}
for _, tab in ipairs(cWebAdmin:GetAllWebTabs()) do
local pluginTabs = perPluginTabs[tab.PluginName] or {};
perPluginTabs[tab.PluginName] = pluginTabs
table.insert(pluginTabs, tab)
end
-- Sort by plugin:
local pluginNames = {}
for pluginName, pluginTabs in pairs(perPluginTabs) do
table.insert(pluginNames, pluginName)
end
table.sort(pluginNames)
-- Output by plugin, then alphabetically:
for _, pluginName in ipairs(pluginNames) do
local pluginTabs = perPluginTabs[pluginName]
table.sort(pluginTabs,
function(a_Tab1, a_Tab2)
return ((a_Tab1.Title or "") < (a_Tab2.Title or ""))
end
)
-- Translate the plugin name into the folder name (-> title)
local pluginWebTitle = cPluginManager:Get():GetPluginFolderName(pluginName) or pluginName
Output("<div><a class='usercp_nav_item usercp_nav_pmfolder' style='text-decoration:none;'><b>" .. pluginWebTitle .. "</b></a></div>\n");
Output("<br>\n");
-- Output each tab:
for _, tab in pairs(pluginTabs) do
Output("<div><a href='" .. BaseURL .. pluginName .. "/" .. tab.UrlPath .. "' class='usercp_nav_item usercp_nav_sub_pmfolder'>" .. tab.Title .. "</a></div>\n")
end
Output("<br>\n");
end

View File

@ -38,7 +38,6 @@ $cfile "LuaFunctions.h"
$cfile "PluginManager.h"
$cfile "Plugin.h"
$cfile "PluginLua.h"
$cfile "WebPlugin.h"
$cfile "LuaWindow.h"
$cfile "../BlockID.h"

View File

@ -23,7 +23,6 @@ SET (SRCS
Plugin.cpp
PluginLua.cpp
PluginManager.cpp
WebPlugin.cpp
)
SET (HDRS
@ -44,7 +43,6 @@ SET (HDRS
Plugin.h
PluginLua.h
PluginManager.h
WebPlugin.h
tolua++.h
)
@ -66,7 +64,6 @@ set(BINDING_DEPENDENCIES
../Bindings/Plugin.h
../Bindings/PluginLua.h
../Bindings/PluginManager.h
../Bindings/WebPlugin.h
../BiomeDef.h
../BlockArea.h
../BlockEntities/BeaconEntity.h

View File

@ -113,12 +113,10 @@ void cLuaChunkStay::AddChunkCoord(cLuaState & L, int a_Index)
void cLuaChunkStay::Enable(cChunkMap & a_ChunkMap, int a_OnChunkAvailableStackPos, int a_OnAllChunksAvailableStackPos)
void cLuaChunkStay::Enable(cChunkMap & a_ChunkMap, cLuaState::cCallbackPtr a_OnChunkAvailable, cLuaState::cCallbackPtr a_OnAllChunksAvailable)
{
// Get the references to the callback functions:
m_LuaState = &m_Plugin.GetLuaState();
m_OnChunkAvailable.RefStack(*m_LuaState, a_OnChunkAvailableStackPos);
m_OnAllChunksAvailable.RefStack(*m_LuaState, a_OnAllChunksAvailableStackPos);
m_OnChunkAvailable = std::move(a_OnChunkAvailable);
m_OnAllChunksAvailable = std::move(a_OnAllChunksAvailable);
// Enable the ChunkStay:
super::Enable(a_ChunkMap);
@ -130,10 +128,9 @@ void cLuaChunkStay::Enable(cChunkMap & a_ChunkMap, int a_OnChunkAvailableStackPo
void cLuaChunkStay::OnChunkAvailable(int a_ChunkX, int a_ChunkZ)
{
if (m_OnChunkAvailable.IsValid())
if (m_OnChunkAvailable != nullptr)
{
cPluginLua::cOperation Op(m_Plugin);
Op().Call(static_cast<int>(m_OnChunkAvailable), a_ChunkX, a_ChunkZ);
m_OnChunkAvailable->Call(a_ChunkX, a_ChunkZ);
}
}
@ -143,15 +140,14 @@ void cLuaChunkStay::OnChunkAvailable(int a_ChunkX, int a_ChunkZ)
bool cLuaChunkStay::OnAllChunksAvailable(void)
{
if (m_OnAllChunksAvailable.IsValid())
if (m_OnAllChunksAvailable != nullptr)
{
// Call the callback:
cPluginLua::cOperation Op(m_Plugin);
Op().Call(static_cast<int>(m_OnAllChunksAvailable));
m_OnAllChunksAvailable->Call();
// Remove the callback references - they won't be needed anymore
m_OnChunkAvailable.UnRef();
m_OnAllChunksAvailable.UnRef();
m_OnChunkAvailable.reset();
m_OnAllChunksAvailable.reset();
}
// Disable the ChunkStay by returning true

View File

@ -39,7 +39,7 @@ public:
bool AddChunks(int a_ChunkCoordTableStackPos);
/** Enables the ChunkStay for the specified chunkmap, with the specified Lua callbacks. */
void Enable(cChunkMap & a_ChunkMap, int a_OnChunkAvailableStackPos, int a_OnAllChunksAvailableStackPos);
void Enable(cChunkMap & a_ChunkMap, cLuaState::cCallbackPtr a_OnChunkAvailable, cLuaState::cCallbackPtr a_OnAllChunksAvailable);
protected:
/** The plugin which has created the ChunkStay, via cWorld:ChunkStay() binding method. */
@ -49,10 +49,10 @@ protected:
cLuaState * m_LuaState;
/** The Lua function to call in OnChunkAvailable. Only valid when enabled. */
cLuaState::cRef m_OnChunkAvailable;
cLuaState::cCallbackPtr m_OnChunkAvailable;
/** The Lua function to call in OnAllChunksAvailable. Only valid when enabled. */
cLuaState::cRef m_OnAllChunksAvailable;
cLuaState::cCallbackPtr m_OnAllChunksAvailable;
// cChunkStay overrides:

View File

@ -12,7 +12,7 @@
cLuaNameLookup::cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
m_Plugin(a_Plugin),
m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos),
m_Callbacks(cPluginLua::cOperation(a_Plugin)(), a_CallbacksTableStackPos),
m_Query(a_Query)
{
}

View File

@ -14,7 +14,7 @@
cLuaServerHandle::cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
m_Plugin(a_Plugin),
m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos),
m_Callbacks(cPluginLua::cOperation(a_Plugin)(), a_CallbacksTableStackPos),
m_Port(a_Port)
{
}

View File

@ -20,6 +20,10 @@ extern "C"
#include "../Entities/Entity.h"
#include "../BlockEntities/BlockEntity.h"
// fwd: "SQLite/lsqlite3.c"
extern "C"
{
@ -39,6 +43,10 @@ extern "C"
const cLuaState::cRet cLuaState::Return = {};
/** Each Lua state stores a pointer to its creating cLuaState in Lua globals, under this name.
This way any cLuaState can reference the main cLuaState's TrackedCallbacks, mutex etc. */
static const char * g_CanonLuaStateGlobalName = "_CuberiteInternal_CanonLuaState";
@ -113,6 +121,135 @@ cLuaStateTracker & cLuaStateTracker::Get(void)
////////////////////////////////////////////////////////////////////////////////
// cLuaState::cCallback:
cLuaState::cCallback::cCallback(void):
m_CS(nullptr)
{
}
bool cLuaState::cCallback::RefStack(cLuaState & a_LuaState, int a_StackPos)
{
// Check if the stack contains a function:
if (!lua_isfunction(a_LuaState, a_StackPos))
{
return false;
}
// Clear any previous callback:
Clear();
// Add self to LuaState's callback-tracking:
auto canonState = a_LuaState.QueryCanonLuaState();
canonState->TrackCallback(*this);
// Store the new callback:
m_CS = &(canonState->m_CS);
m_Ref.RefStack(*canonState, a_StackPos);
return true;
}
void cLuaState::cCallback::Clear(void)
{
// Free the callback reference:
lua_State * luaState = nullptr;
{
auto cs = m_CS;
if (cs != nullptr)
{
cCSLock Lock(*cs);
if (!m_Ref.IsValid())
{
return;
}
luaState = m_Ref.GetLuaState();
m_Ref.UnRef();
}
}
// Remove from LuaState's callback-tracking:
if (luaState == nullptr)
{
return;
}
cLuaState(luaState).UntrackCallback(*this);
}
bool cLuaState::cCallback::IsValid(void)
{
auto cs = m_CS;
if (cs == nullptr)
{
return false;
}
cCSLock lock(*cs);
return m_Ref.IsValid();
}
bool cLuaState::cCallback::IsSameLuaState(cLuaState & a_LuaState)
{
auto cs = m_CS;
if (cs == nullptr)
{
return false;
}
cCSLock lock(*cs);
if (!m_Ref.IsValid())
{
return false;
}
auto canonState = a_LuaState.QueryCanonLuaState();
if (canonState == nullptr)
{
return false;
}
return (m_Ref.GetLuaState() == static_cast<lua_State *>(*canonState));
}
void cLuaState::cCallback::Invalidate(void)
{
auto cs = m_CS;
if (cs == nullptr)
{
// Already invalid
return;
}
cCSLock Lock(*cs);
if (!m_Ref.IsValid())
{
LOGD("%s: Inconsistent callback at %p, has a CS but an invalid Ref. This should not happen",
__FUNCTION__, reinterpret_cast<void *>(this)
);
return;
}
m_Ref.UnRef();
}
////////////////////////////////////////////////////////////////////////////////
// cLuaState:
@ -170,6 +307,10 @@ void cLuaState::Create(void)
luaL_openlibs(m_LuaState);
m_IsOwned = true;
cLuaStateTracker::Add(*this);
// Add the CanonLuaState value into the Lua state, so that we can get it from anywhere:
lua_pushlightuserdata(m_LuaState, reinterpret_cast<void *>(this));
lua_setglobal(m_LuaState, g_CanonLuaStateGlobalName);
}
@ -206,6 +347,16 @@ void cLuaState::Close(void)
Detach();
return;
}
// Invalidate all callbacks:
{
cCSLock Lock(m_CSTrackedCallbacks);
for (auto & c: m_TrackedCallbacks)
{
c->Invalidate();
}
}
cLuaStateTracker::Del(*this);
lua_close(m_LuaState);
m_LuaState = nullptr;
@ -402,7 +553,7 @@ bool cLuaState::PushFunction(const char * a_FunctionName)
bool cLuaState::PushFunction(int a_FnRef)
bool cLuaState::PushFunction(const cRef & a_FnRef)
{
ASSERT(IsValid());
ASSERT(m_NumCurrentFunctionArgs == -1); // If not, there's already something pushed onto the stack
@ -410,7 +561,7 @@ bool cLuaState::PushFunction(int a_FnRef)
// Push the error handler for lua_pcall()
lua_pushcfunction(m_LuaState, &ReportFnCallErrors);
lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, a_FnRef); // same as lua_getref()
lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, static_cast<int>(a_FnRef)); // same as lua_getref()
if (!lua_isfunction(m_LuaState, -1))
{
lua_pop(m_LuaState, 2);
@ -821,6 +972,18 @@ void cLuaState::Push(std::chrono::milliseconds a_Value)
void cLuaState::Pop(int a_NumValuesToPop)
{
ASSERT(IsValid());
lua_pop(m_LuaState, a_NumValuesToPop);
m_NumCurrentFunctionArgs -= a_NumValuesToPop;
}
bool cLuaState::GetStackValue(int a_StackPos, AString & a_Value)
{
size_t len = 0;
@ -847,6 +1010,41 @@ bool cLuaState::GetStackValue(int a_StackPos, bool & a_ReturnedVal)
bool cLuaState::GetStackValue(int a_StackPos, cCallback & a_Callback)
{
return a_Callback.RefStack(*this, a_StackPos);
}
bool cLuaState::GetStackValue(int a_StackPos, cCallbackPtr & a_Callback)
{
if (a_Callback == nullptr)
{
a_Callback = cpp14::make_unique<cCallback>();
}
return a_Callback->RefStack(*this, a_StackPos);
}
bool cLuaState::GetStackValue(int a_StackPos, cCallbackSharedPtr & a_Callback)
{
if (a_Callback == nullptr)
{
a_Callback = std::make_shared<cCallback>();
}
return a_Callback->RefStack(*this, a_StackPos);
}
bool cLuaState::GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result)
{
if (lua_isnumber(m_LuaState, a_StackPos))
@ -1632,16 +1830,16 @@ void cLuaState::ToString(int a_StackPos, AString & a_String)
void cLuaState::LogStack(const char * a_Header)
void cLuaState::LogStackValues(const char * a_Header)
{
LogStack(m_LuaState, a_Header);
LogStackValues(m_LuaState, a_Header);
}
void cLuaState::LogStack(lua_State * a_LuaState, const char * a_Header)
void cLuaState::LogStackValues(lua_State * a_LuaState, const char * a_Header)
{
// Format string consisting only of %s is used to appease the compiler
LOG("%s", (a_Header != nullptr) ? a_Header : "Lua C API Stack contents:");
@ -1667,6 +1865,21 @@ void cLuaState::LogStack(lua_State * a_LuaState, const char * a_Header)
cLuaState * cLuaState::QueryCanonLuaState(void)
{
// Get the CanonLuaState global from Lua:
auto cb = WalkToNamedGlobal(g_CanonLuaStateGlobalName);
if (!cb.IsValid())
{
return nullptr;
}
return reinterpret_cast<cLuaState *>(lua_touserdata(m_LuaState, -1));
}
int cLuaState::ReportFnCallErrors(lua_State * a_LuaState)
{
LOGWARNING("LUA: %s", lua_tostring(a_LuaState, -1));
@ -1701,6 +1914,50 @@ int cLuaState::BreakIntoDebugger(lua_State * a_LuaState)
void cLuaState::TrackCallback(cCallback & a_Callback)
{
// Get the CanonLuaState global from Lua:
auto canonState = QueryCanonLuaState();
if (canonState == nullptr)
{
LOGWARNING("%s: Lua state %p has invalid CanonLuaState!", __FUNCTION__, reinterpret_cast<void *>(m_LuaState));
return;
}
// Add the callback:
cCSLock Lock(canonState->m_CSTrackedCallbacks);
canonState->m_TrackedCallbacks.push_back(&a_Callback);
}
void cLuaState::UntrackCallback(cCallback & a_Callback)
{
// Get the CanonLuaState global from Lua:
auto canonState = QueryCanonLuaState();
if (canonState == nullptr)
{
LOGWARNING("%s: Lua state %p has invalid CanonLuaState!", __FUNCTION__, reinterpret_cast<void *>(m_LuaState));
return;
}
// Remove the callback:
cCSLock Lock(canonState->m_CSTrackedCallbacks);
auto & trackedCallbacks = canonState->m_TrackedCallbacks;
trackedCallbacks.erase(std::remove_if(trackedCallbacks.begin(), trackedCallbacks.end(),
[&a_Callback](cCallback * a_StoredCallback)
{
return (a_StoredCallback == &a_Callback);
}
));
}
////////////////////////////////////////////////////////////////////////////////
// cLuaState::cRef:
@ -1756,7 +2013,7 @@ void cLuaState::cRef::RefStack(cLuaState & a_LuaState, int a_StackPos)
{
UnRef();
}
m_LuaState = &a_LuaState;
m_LuaState = a_LuaState;
lua_pushvalue(a_LuaState, a_StackPos); // Push a copy of the value at a_StackPos onto the stack
m_Ref = luaL_ref(a_LuaState, LUA_REGISTRYINDEX);
}
@ -1767,11 +2024,9 @@ void cLuaState::cRef::RefStack(cLuaState & a_LuaState, int a_StackPos)
void cLuaState::cRef::UnRef(void)
{
ASSERT(m_LuaState->IsValid()); // The reference should be destroyed before destroying the LuaState
if (IsValid())
{
luaL_unref(*m_LuaState, LUA_REGISTRYINDEX, m_Ref);
luaL_unref(m_LuaState, LUA_REGISTRYINDEX, m_Ref);
}
m_LuaState = nullptr;
m_Ref = LUA_REFNIL;

View File

@ -7,7 +7,7 @@
The contained lua_State can be either owned or attached.
Owned lua_State is created by calling Create() and the cLuaState automatically closes the state
Or, lua_State can be attached by calling Attach(), the cLuaState doesn't close such a state
Attaching a state will automatically close an owned state.
If owning a state, trying to attach a state will automatically close the previously owned state.
Calling a Lua function is done internally by pushing the function using PushFunction(), then pushing the
arguments and finally executing CallFunction(). cLuaState automatically keeps track of the number of
@ -16,8 +16,12 @@ the stack using GetStackValue(). All of this is wrapped in a templated function
which is generated automatically by gen_LuaState_Call.lua script file into the LuaState_Call.inc file.
Reference management is provided by the cLuaState::cRef class. This is used when you need to hold a reference to
any Lua object across several function calls; usually this is used for callbacks. The class is RAII-like, with
automatic resource management.
any Lua object across several function calls. The class is RAII-like, with automatic resource management.
Callbacks management is provided by the cLuaState::cCallback class. Use a GetStackValue() with cCallbackPtr
parameter to store the callback, and then at any time you can use the cCallback's Call() templated function
to call the callback. The callback management takes care of cLuaState being destroyed - the cCallback object
stays valid but doesn't call into Lua code anymore, returning false for "failure" instead.
*/
@ -39,6 +43,7 @@ extern "C"
class cLuaServerHandle;
class cLuaTCPLink;
class cLuaUDPEndpoint;
class cPluginLua;
@ -49,6 +54,20 @@ class cLuaState
{
public:
/** Provides a RAII-style locking for the LuaState.
Used mainly by the cPluginLua internals to provide the actual locking for interface operations, such as callbacks. */
class cLock
{
public:
cLock(cLuaState & a_LuaState):
m_Lock(a_LuaState.m_CS)
{
}
protected:
cCSLock m_Lock;
};
/** Used for storing references to object in the global registry.
Can be bound (contains a reference) or unbound (doesn't contain reference).
The reference can also be reset by calling RefStack(). */
@ -80,8 +99,20 @@ public:
/** Allows to use this class wherever an int (i. e. ref) is to be used */
explicit operator int(void) const { return m_Ref; }
/** Returns the Lua state associated with the value. */
lua_State * GetLuaState(void) { return m_LuaState; }
/** Creates a Lua reference to the specified object instance in the specified Lua state.
This is useful to make anti-GC references for objects that were created by Lua and need to stay alive longer than Lua GC would normally guarantee. */
template <typename T> void CreateFromObject(cLuaState & a_LuaState, T && a_Object)
{
a_LuaState.Push(std::forward<T>(a_Object));
RefStack(a_LuaState, -1);
a_LuaState.Pop();
}
protected:
cLuaState * m_LuaState;
lua_State * m_LuaState;
int m_Ref;
// Remove the copy-constructor:
@ -112,6 +143,83 @@ public:
} ;
/** Represents a callback to Lua that C++ code can call.
Is thread-safe and unload-safe.
When the Lua state is unloaded, the callback returns an error instead of calling into non-existent code.
To receive the callback instance from the Lua side, use RefStack() or (better) cLuaState::GetStackValue()
with a cCallbackPtr. Note that instances of this class are tracked in the canon LuaState instance, so that
they can be invalidated when the LuaState is unloaded; due to multithreading issues they can only be tracked
by-ptr, which has an unfortunate effect of disabling the copy and move constructors. */
class cCallback
{
public:
/** Creates an unbound callback instance. */
cCallback(void);
~cCallback()
{
Clear();
}
/** Calls the Lua callback, if still available.
Returns true if callback has been called.
Returns false if the Lua state isn't valid anymore. */
template <typename... Args>
bool Call(Args &&... args)
{
auto cs = m_CS;
if (cs == nullptr)
{
return false;
}
cCSLock Lock(*cs);
if (!m_Ref.IsValid())
{
return false;
}
return cLuaState(m_Ref.GetLuaState()).Call(m_Ref, std::forward<Args>(args)...);
}
/** Set the contained callback to the function in the specified Lua state's stack position.
If a callback has been previously contained, it is freed first. */
bool RefStack(cLuaState & a_LuaState, int a_StackPos);
/** Frees the contained callback, if any. */
void Clear(void);
/** Returns true if the contained callback is valid. */
bool IsValid(void);
/** Returns true if the callback resides in the specified Lua state.
Internally, compares the callback's canon Lua state. */
bool IsSameLuaState(cLuaState & a_LuaState);
protected:
friend class cLuaState;
/** The mutex protecting m_Ref against multithreaded access */
cCriticalSection * m_CS;
/** Reference to the Lua callback */
cRef m_Ref;
/** Invalidates the callback, without untracking it from the cLuaState.
Called only from cLuaState when closing the Lua state. */
void Invalidate(void);
/** This class cannot be copied, because it is tracked in the LuaState by-ptr.
Use cCallbackPtr for a copyable object. */
cCallback(const cCallback &) = delete;
/** This class cannot be moved, because it is tracked in the LuaState by-ptr.
Use cCallbackPtr for a copyable object. */
cCallback(cCallback &&) = delete;
};
typedef UniquePtr<cCallback> cCallbackPtr;
typedef SharedPtr<cCallback> cCallbackSharedPtr;
/** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */
class cRet
{
@ -168,6 +276,8 @@ public:
protected:
lua_State * m_LuaState;
/** Used for debugging, Lua state's stack size is checked against this number to make sure
it is the same as when the value was pushed. */
int m_StackLen;
// Remove the copy-constructor:
@ -261,11 +371,17 @@ public:
void Push(const UInt32 a_Value);
void Push(std::chrono::milliseconds a_time);
/** Pops the specified number of values off the top of the Lua stack. */
void Pop(int a_NumValuesToPop = 1);
// GetStackValue() retrieves the value at a_StackPos, if it is a valid type. If not, a_Value is unchanged.
// Returns whether value was changed
// Enum values are checked for their allowed values and fail if the value is not assigned.
bool GetStackValue(int a_StackPos, AString & a_Value);
bool GetStackValue(int a_StackPos, bool & a_Value);
bool GetStackValue(int a_StackPos, cCallback & a_Callback);
bool GetStackValue(int a_StackPos, cCallbackPtr & a_Callback);
bool GetStackValue(int a_StackPos, cCallbackSharedPtr & a_Callback);
bool GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result);
bool GetStackValue(int a_StackPos, cRef & a_Ref);
bool GetStackValue(int a_StackPos, double & a_Value);
@ -440,21 +556,26 @@ public:
void ToString(int a_StackPos, AString & a_String);
/** Logs all the elements' types on the API stack, with an optional header for the listing. */
void LogStack(const char * a_Header = nullptr);
void LogStackValues(const char * a_Header = nullptr);
/** Logs all the elements' types on the API stack, with an optional header for the listing. */
static void LogStack(lua_State * a_LuaState, const char * a_Header = nullptr);
static void LogStackValues(lua_State * a_LuaState, const char * a_Header = nullptr);
/** Returns the canon Lua state (the primary cLuaState instance that was used to create, rather than attach, the lua_State structure).
Returns nullptr if the canon Lua state cannot be queried. */
cLuaState * QueryCanonLuaState(void);
protected:
cCriticalSection m_CS;
lua_State * m_LuaState;
/** If true, the state is owned by this object and will be auto-Closed. False => attached state */
bool m_IsOwned;
/** The subsystem name is used for reporting errors to the console, it is either "plugin %s" or "LuaScript"
whatever is given to the constructor
*/
whatever is given to the constructor. */
AString m_SubsystemName;
/** Name of the currently pushed function (for the Push / Call chain) */
@ -463,6 +584,15 @@ protected:
/** Number of arguments currently pushed (for the Push / Call chain) */
int m_NumCurrentFunctionArgs;
/** The tracked callbacks.
This object will invalidate all of these when it is about to be closed.
Protected against multithreaded access by m_CSTrackedCallbacks. */
std::vector<cCallback *> m_TrackedCallbacks;
/** Protects m_TrackedTallbacks against multithreaded access. */
cCriticalSection m_CSTrackedCallbacks;
/** Variadic template terminator: If there's nothing more to push / pop, just call the function.
Note that there are no return values either, because those are prefixed by a cRet value, so the arg list is never empty. */
bool PushCallPop(void)
@ -512,18 +642,10 @@ protected:
*/
bool PushFunction(const char * a_FunctionName);
/** Pushes a function that has been saved into the global registry, identified by a_FnRef.
Returns true if successful. Logs a warning on failure
*/
bool PushFunction(int a_FnRef);
/** Pushes a function that has been saved as a reference.
Returns true if successful. Logs a warning on failure
*/
bool PushFunction(const cRef & a_FnRef)
{
return PushFunction(static_cast<int>(a_FnRef));
}
bool PushFunction(const cRef & a_FnRef);
/** Pushes a function that is stored in a referenced table by name
Returns true if successful. Logs a warning on failure
@ -545,6 +667,14 @@ protected:
/** Tries to break into the MobDebug debugger, if it is installed. */
static int BreakIntoDebugger(lua_State * a_LuaState);
/** Adds the specified callback to tracking.
The callback will be invalidated when this Lua state is about to be closed. */
void TrackCallback(cCallback & a_Callback);
/** Removes the specified callback from tracking.
The callback will no longer be invalidated when this Lua state is about to be closed. */
void UntrackCallback(cCallback & a_Callback);
} ;

View File

@ -13,7 +13,7 @@
cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
m_Plugin(a_Plugin),
m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos)
m_Callbacks(cPluginLua::cOperation(a_Plugin)(), a_CallbacksTableStackPos)
{
// Warn if the callbacks aren't valid:
if (!m_Callbacks.IsValid())

View File

@ -12,7 +12,7 @@
cLuaUDPEndpoint::cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
m_Plugin(a_Plugin),
m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos)
m_Callbacks(cPluginLua::cOperation(a_Plugin)(), a_CallbacksTableStackPos)
{
// Warn if the callbacks aren't valid:
if (!m_Callbacks.IsValid())

View File

@ -15,14 +15,13 @@
////////////////////////////////////////////////////////////////////////////////
// cLuaWindow:
cLuaWindow::cLuaWindow(cWindow::WindowType a_WindowType, int a_SlotsX, int a_SlotsY, const AString & a_Title) :
super(a_WindowType, a_Title),
cLuaWindow::cLuaWindow(cLuaState & a_LuaState, cWindow::WindowType a_WindowType, int a_SlotsX, int a_SlotsY, const AString & a_Title) :
Super(a_WindowType, a_Title),
m_Contents(a_SlotsX, a_SlotsY),
m_Plugin(nullptr),
m_LuaRef(LUA_REFNIL),
m_OnClosingFnRef(LUA_REFNIL),
m_OnSlotChangedFnRef(LUA_REFNIL)
m_LuaState(a_LuaState.QueryCanonLuaState())
{
ASSERT(m_LuaState != nullptr); // We must have a valid Lua state; this assert fails only if there was no Canon Lua state
m_Contents.AddListener(*this);
m_SlotAreas.push_back(new cSlotAreaItemGrid(m_Contents, *this));
@ -67,62 +66,42 @@ cLuaWindow::~cLuaWindow()
void cLuaWindow::SetLuaRef(cPluginLua * a_Plugin, int a_LuaRef)
void cLuaWindow::SetOnClosing(cLuaState::cCallbackPtr && a_OnClosing)
{
// Either m_Plugin is not set or equal to the passed plugin; only one plugin can use one cLuaWindow object
ASSERT((m_Plugin == nullptr) || (m_Plugin == a_Plugin));
ASSERT(m_LuaRef == LUA_REFNIL);
m_Plugin = a_Plugin;
m_LuaRef = a_LuaRef;
// Only one Lua state can be a cLuaWindow object callback:
ASSERT(a_OnClosing->IsSameLuaState(*m_LuaState));
// Store the new reference, releasing the old one if appropriate:
m_OnClosing = std::move(a_OnClosing);
}
bool cLuaWindow::IsLuaReferenced(void) const
void cLuaWindow::SetOnSlotChanged(cLuaState::cCallbackPtr && a_OnSlotChanged)
{
return ((m_Plugin != nullptr) && (m_LuaRef != LUA_REFNIL));
// Only one Lua state can be a cLuaWindow object callback:
ASSERT(a_OnSlotChanged->IsSameLuaState(*m_LuaState));
// Store the new reference, releasing the old one if appropriate:
m_OnSlotChanged = std::move(a_OnSlotChanged);
}
void cLuaWindow::SetOnClosing(cPluginLua * a_Plugin, int a_FnRef)
void cLuaWindow::OpenedByPlayer(cPlayer & a_Player)
{
// Either m_Plugin is not set or equal to the passed plugin; only one plugin can use one cLuaWindow object
ASSERT((m_Plugin == nullptr) || (m_Plugin == a_Plugin));
// If there already was a function, unreference it first
if (m_OnClosingFnRef != LUA_REFNIL)
// If the first player is opening the window, create a Lua Reference to the window object so that Lua will not GC it until the last player closes the window:
if (m_PlayerCount == 0)
{
m_Plugin->Unreference(m_OnClosingFnRef);
m_LuaRef.CreateFromObject(*m_LuaState, this);
}
// Store the new reference
m_Plugin = a_Plugin;
m_OnClosingFnRef = a_FnRef;
}
void cLuaWindow::SetOnSlotChanged(cPluginLua * a_Plugin, int a_FnRef)
{
// Either m_Plugin is not set or equal to the passed plugin; only one plugin can use one cLuaWindow object
ASSERT((m_Plugin == nullptr) || (m_Plugin == a_Plugin));
// If there already was a function, unreference it first
if (m_OnSlotChangedFnRef != LUA_REFNIL)
{
m_Plugin->Unreference(m_OnSlotChangedFnRef);
}
// Store the new reference
m_Plugin = a_Plugin;
m_OnSlotChangedFnRef = a_FnRef;
++m_PlayerCount;
Super::OpenedByPlayer(a_Player);
}
@ -132,17 +111,27 @@ void cLuaWindow::SetOnSlotChanged(cPluginLua * a_Plugin, int a_FnRef)
bool cLuaWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse)
{
// First notify the plugin through the registered callback:
if (m_OnClosingFnRef != LUA_REFNIL)
if (m_OnClosing != nullptr)
{
ASSERT(m_Plugin != nullptr);
if (m_Plugin->CallbackWindowClosing(m_OnClosingFnRef, *this, a_Player, a_CanRefuse))
bool res;
if (
m_OnClosing->Call(this, &a_Player, a_CanRefuse, cLuaState::Return, res) && // The callback succeeded
res // The callback says not to close the window
)
{
// The callback disagrees (the higher levels check the CanRefuse flag compliance)
return false;
}
}
return super::ClosedByPlayer(a_Player, a_CanRefuse);
// If the last player has closed the window, release the Lua reference, so that Lua may GC the object:
--m_PlayerCount;
if (m_PlayerCount == 0)
{
m_LuaRef.UnRef();
}
return Super::ClosedByPlayer(a_Player, a_CanRefuse);
}
@ -151,13 +140,7 @@ bool cLuaWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse)
void cLuaWindow::Destroy(void)
{
super::Destroy();
if ((m_LuaRef != LUA_REFNIL) && (m_Plugin != nullptr))
{
// The object is referenced by Lua, un-reference it
m_Plugin->Unreference(m_LuaRef);
}
Super::Destroy();
// Lua will take care of this object, it will garbage-collect it, so we must not delete it!
m_IsDestroyed = false;
@ -178,7 +161,7 @@ void cLuaWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Pl
}
}
super::DistributeStackToAreas(a_ItemStack, a_Player, Areas, a_ShouldApply, false);
Super::DistributeStackToAreas(a_ItemStack, a_Player, Areas, a_ShouldApply, false);
}
@ -194,9 +177,9 @@ void cLuaWindow::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
}
// If an OnSlotChanged callback has been registered, call it:
if (m_OnSlotChangedFnRef != LUA_REFNIL)
if (m_OnSlotChanged != nullptr)
{
m_Plugin->CallbackWindowSlotChanged(m_OnSlotChangedFnRef, *this, a_SlotNum);
m_OnSlotChanged->Call(this, a_SlotNum);
}
}

View File

@ -9,6 +9,8 @@
#pragma once
#include <atomic>
#include "LuaState.h"
#include "../UI/Window.h"
#include "../ItemGrid.h"
@ -16,35 +18,30 @@
// fwd: PluginLua.h
class cPluginLua;
/** A window that has been created by a Lua plugin and is handled entirely by that plugin
This object needs extra care with its lifetime management:
- It is created by Lua, so Lua expects to garbage-collect it later
- normal cWindow objects are deleted in their ClosedByPlayer() function if the last player closes them
To overcome this, this object overloads the Destroy functions, which doesn't let the ClosedByPlayer()
delete the window, but rather leaves it dangling, with only Lua having the reference to it.
Additionally, to forbid Lua from deleting this object while it is used by players, the manual bindings for
cPlayer:OpenWindow check if the window is of this class, and if so, make a global Lua reference for this object.
This reference needs to be unreferenced in the Destroy() function. */
- Normal cWindow objects are deleted in their ClosedByPlayer() function if the last player closes them
To overcome this, this object overloads the Destroy functions, which doesn't let the ClosedByPlayer()
delete the window, but rather leaves it dangling, with only Lua having the reference to it.
- Lua could GC the window while a player is still using it
The object creates a Lua reference to itself when opened by a player and
removes the reference when the last player closes the window.
*/
// tolua_begin
class cLuaWindow :
public cWindow
// tolua_end
, public cItemGrid::cListener
// tolua_begin
{
typedef cWindow super;
{ // tolua_export
typedef cWindow Super;
public:
/** Create a window of the specified type, with a slot grid of a_SlotsX * a_SlotsY size */
cLuaWindow(cWindow::WindowType a_WindowType, int a_SlotsX, int a_SlotsY, const AString & a_Title);
/** Create a window of the specified type, with a slot grid of a_SlotsX * a_SlotsY size.
Exported in ManualBindings.cpp */
cLuaWindow(cLuaState & a_LuaState, cWindow::WindowType a_WindowType, int a_SlotsX, int a_SlotsY, const AString & a_Title);
// tolua_begin
virtual ~cLuaWindow();
/** Returns the internal representation of the contents that are manipulated by Lua */
@ -52,36 +49,37 @@ public:
// tolua_end
/** Sets the plugin reference and the internal Lua object reference index
used for preventing Lua's GC to collect this class while the window is open. */
void SetLuaRef(cPluginLua * a_Plugin, int a_LuaRef);
/** Sets the Lua callback function to call when the window is about to close */
void SetOnClosing(cLuaState::cCallbackPtr && a_OnClosing);
/** Returns true if SetLuaRef() has been called */
bool IsLuaReferenced(void) const;
/** Sets the callback function (Lua reference) to call when the window is about to close */
void SetOnClosing(cPluginLua * a_Plugin, int a_FnRef);
/** Sets the callback function (Lua reference) to call when a slot is changed */
void SetOnSlotChanged(cPluginLua * a_Plugin, int a_FnRef);
/** Sets the Lua callback function to call when a slot is changed */
void SetOnSlotChanged(cLuaState::cCallbackPtr && a_OnSlotChanged);
protected:
/** Contents of the non-inventory part */
cItemGrid m_Contents;
/** The plugin that has opened the window and owns the m_LuaRef */
cPluginLua * m_Plugin;
/** The Lua state that has opened the window and owns the m_LuaRef */
cLuaState * m_LuaState;
/** The Lua object reference, used for keeping the object alive as long as any player has the window open */
int m_LuaRef;
/** The Lua callback to call when the window is closing for any player */
cLuaState::cCallbackPtr m_OnClosing;
/** The Lua reference for the callback to call when the window is closing for any player */
int m_OnClosingFnRef;
/** The Lua callback to call when a slot has changed */
cLuaState::cCallbackPtr m_OnSlotChanged;
/** Number of players that are currently using the window.
Used to manager the m_LuaRef lifetime. */
std::atomic<int> m_PlayerCount;
/** Reference to self, to keep Lua from GCing the object while a player is still using it.
Created when the first player opens the window, destroyed when the last player closes the window. */
cLuaState::cRef m_LuaRef;
/** The Lua reference for the callback to call when a slot has changed */
int m_OnSlotChangedFnRef;
// cWindow overrides:
virtual void OpenedByPlayer(cPlayer & a_Player) override;
virtual bool ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) override;
virtual void Destroy(void) override;
virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override;

View File

@ -43,6 +43,50 @@
////////////////////////////////////////////////////////////////////////////////
// LuaCommandHandler:
/** Defines a bridge between cPluginManager::cCommandHandler and cLuaState::cCallback. */
class LuaCommandHandler:
public cPluginManager::cCommandHandler
{
public:
LuaCommandHandler(cLuaState::cCallbackPtr && a_Callback):
m_Callback(std::move(a_Callback))
{
}
virtual bool ExecuteCommand(
const AStringVector & a_Split,
cPlayer * a_Player,
const AString & a_Command,
cCommandOutputCallback * a_Output
) override
{
bool res = false;
AString s;
if (!m_Callback->Call(a_Split, a_Player, a_Command, cLuaState::Return, res, s))
{
return false;
}
if (res && (a_Output != nullptr) && !s.empty())
{
a_Output->Out(s);
}
return res;
}
protected:
cLuaState::cCallbackPtr m_Callback;
};
////////////////////////////////////////////////////////////////////////////////
// cManualBindings:
// Better error reporting for Lua
int cManualBindings::tolua_do_error(lua_State * L, const char * a_pMsg, tolua_Error * a_pToLuaError)
{
@ -993,7 +1037,13 @@ static int tolua_cPluginManager_AddHook_FnRef(cPluginManager * a_PluginManager,
}
// Retrieve and check the hook type
int HookType = static_cast<int>(tolua_tonumber(S, a_ParamIdx, -1));
int HookType;
if (!S.GetStackValue(a_ParamIdx, HookType))
{
LOGWARNING("cPluginManager.AddHook(): Cannot read the hook type.");
S.LogStackTrace();
return 0;
}
if (!a_PluginManager->IsValidHookType(HookType))
{
LOGWARNING("cPluginManager.AddHook(): Invalid HOOK_TYPE parameter: %d", HookType);
@ -1002,7 +1052,14 @@ static int tolua_cPluginManager_AddHook_FnRef(cPluginManager * a_PluginManager,
}
// Add the hook to the plugin
if (!Plugin->AddHookRef(HookType, a_ParamIdx + 1))
cLuaState::cCallbackPtr callback;
if (!S.GetStackValue(a_ParamIdx + 1, callback))
{
LOGWARNING("cPluginManager.AddHook(): Cannot read the callback parameter");
S.LogStackTrace();
return 0;
}
if (!Plugin->AddHookCallback(HookType, std::move(callback)))
{
LOGWARNING("cPluginManager.AddHook(): Cannot add hook %d, unknown error.", HookType);
S.LogStackTrace();
@ -1059,10 +1116,11 @@ static int tolua_cPluginManager_AddHook_DefFn(cPluginManager * a_PluginManager,
}
// Retrieve the function to call and add it to the plugin:
lua_pushstring(S, FnName);
bool res = Plugin->AddHookRef(HookType, 1);
lua_pop(S, 1); // Pop the function off the stack
if (!res)
cLuaState::cCallbackPtr callback;
lua_getglobal(S, FnName);
bool res = S.GetStackValue(-1, callback);
lua_pop(S, 1);
if (!res || !callback->IsValid())
{
LOGWARNING("cPluginManager.AddHook(): Function %s not found. Hook not added.", FnName);
S.LogStackTrace();
@ -1254,12 +1312,13 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S)
static int tolua_cPluginManager_BindCommand(lua_State * L)
static int tolua_cPluginManager_BindCommand(lua_State * a_LuaState)
{
/* Function signatures:
cPluginManager:BindCommand(Command, Permission, Function, HelpString)
cPluginManager.BindCommand(Command, Permission, Function, HelpString) -- without the "self" param
*/
cLuaState L(a_LuaState);
cPluginLua * Plugin = cManualBindings::GetLuaPlugin(L);
if (Plugin == nullptr)
{
@ -1292,29 +1351,24 @@ static int tolua_cPluginManager_BindCommand(lua_State * L)
return 0;
}
cPluginManager * self = cPluginManager::Get();
AString Command (tolua_tocppstring(L, idx, ""));
AString Permission(tolua_tocppstring(L, idx + 1, ""));
AString HelpString(tolua_tocppstring(L, idx + 3, ""));
// Store the function reference:
lua_pop(L, 1); // Pop the help string off the stack
int FnRef = luaL_ref(L, LUA_REGISTRYINDEX); // Store function reference
if (FnRef == LUA_REFNIL)
AString Command, Permission, HelpString;
cLuaState::cCallbackPtr Handler;
L.GetStackValues(idx, Command, Permission, Handler, HelpString);
if (!Handler->IsValid())
{
LOGERROR("\"BindCommand\": Cannot create a function reference. Command \"%s\" not bound.", Command.c_str());
return 0;
}
if (!self->BindCommand(Command, Plugin, Permission, HelpString))
auto CommandHandler = std::make_shared<LuaCommandHandler>(std::move(Handler));
if (!self->BindCommand(Command, Plugin, CommandHandler, Permission, HelpString))
{
// Refused. Possibly already bound. Error message has been given, display the callstack:
cLuaState LS(L);
LS.LogStackTrace();
L.LogStackTrace();
return 0;
}
Plugin->BindCommand(Command, FnRef);
lua_pushboolean(L, true);
L.Push(true);
return 1;
}
@ -1322,7 +1376,7 @@ static int tolua_cPluginManager_BindCommand(lua_State * L)
static int tolua_cPluginManager_BindConsoleCommand(lua_State * L)
static int tolua_cPluginManager_BindConsoleCommand(lua_State * a_LuaState)
{
/* Function signatures:
cPluginManager:BindConsoleCommand(Command, Function, HelpString)
@ -1330,6 +1384,7 @@ static int tolua_cPluginManager_BindConsoleCommand(lua_State * L)
*/
// Get the plugin identification out of LuaState:
cLuaState L(a_LuaState);
cPluginLua * Plugin = cManualBindings::GetLuaPlugin(L);
if (Plugin == nullptr)
{
@ -1361,28 +1416,23 @@ static int tolua_cPluginManager_BindConsoleCommand(lua_State * L)
return 0;
}
cPluginManager * self = cPluginManager::Get();
AString Command (tolua_tocppstring(L, idx, ""));
AString HelpString(tolua_tocppstring(L, idx + 2, ""));
// Store the function reference:
lua_pop(L, 1); // Pop the help string off the stack
int FnRef = luaL_ref(L, LUA_REGISTRYINDEX); // Store function reference
if (FnRef == LUA_REFNIL)
AString Command, HelpString;
cLuaState::cCallbackPtr Handler;
L.GetStackValues(idx, Command, Handler, HelpString);
if (!Handler->IsValid())
{
LOGERROR("\"BindConsoleCommand\": Cannot create a function reference. Console Command \"%s\" not bound.", Command.c_str());
LOGERROR("\"BindConsoleCommand\": Cannot create a function reference. Console command \"%s\" not bound.", Command.c_str());
return 0;
}
if (!self->BindConsoleCommand(Command, Plugin, HelpString))
auto CommandHandler = std::make_shared<LuaCommandHandler>(std::move(Handler));
if (!self->BindConsoleCommand(Command, Plugin, CommandHandler, HelpString))
{
// Refused. Possibly already bound. Error message has been given, display the callstack:
cLuaState LS(L);
LS.LogStackTrace();
L.LogStackTrace();
return 0;
}
Plugin->BindConsoleCommand(Command, FnRef);
lua_pushboolean(L, true);
L.Push(true);
return 1;
}
@ -1586,55 +1636,6 @@ static int tolua_cPlayer_GetRestrictions(lua_State * tolua_S)
static int tolua_cPlayer_OpenWindow(lua_State * tolua_S)
{
// Function signature: cPlayer:OpenWindow(Window)
// Retrieve the plugin instance from the Lua state
cPluginLua * Plugin = cManualBindings::GetLuaPlugin(tolua_S);
if (Plugin == nullptr)
{
return 0;
}
// Get the parameters:
cPlayer * self = reinterpret_cast<cPlayer *>(tolua_tousertype(tolua_S, 1, nullptr));
cWindow * wnd = reinterpret_cast<cWindow *>(tolua_tousertype(tolua_S, 2, nullptr));
if ((self == nullptr) || (wnd == nullptr))
{
LOGWARNING("%s: invalid self (%p) or wnd (%p)", __FUNCTION__, static_cast<void *>(self), static_cast<void *>(wnd));
return 0;
}
// If cLuaWindow, add a reference, so that Lua won't delete the cLuaWindow object mid-processing
tolua_Error err;
if (tolua_isusertype(tolua_S, 2, "cLuaWindow", 0, &err))
{
cLuaWindow * LuaWnd = reinterpret_cast<cLuaWindow *>(wnd);
// Only if not already referenced
if (!LuaWnd->IsLuaReferenced())
{
int LuaRef = luaL_ref(tolua_S, LUA_REGISTRYINDEX);
if (LuaRef == LUA_REFNIL)
{
LOGWARNING("%s: Cannot create a window reference. Cannot open window \"%s\".",
__FUNCTION__, wnd->GetWindowTitle().c_str()
);
return 0;
}
LuaWnd->SetLuaRef(Plugin, LuaRef);
}
}
// Open the window
self->OpenWindow(wnd);
return 0;
}
static int tolua_cPlayer_PermissionMatches(lua_State * tolua_S)
{
// Function signature: cPlayer:PermissionMatches(PermissionStr, TemplateStr) -> bool
@ -1665,36 +1666,25 @@ static int tolua_cPlayer_PermissionMatches(lua_State * tolua_S)
template <
class OBJTYPE,
void (OBJTYPE::*SetCallback)(cPluginLua * a_Plugin, int a_FnRef)
void (OBJTYPE::*SetCallback)(cLuaState::cCallbackPtr && a_CallbackFn)
>
static int tolua_SetObjectCallback(lua_State * tolua_S)
{
// Function signature: OBJTYPE:SetWhateverCallback(CallbackFunction)
// Retrieve the plugin instance from the Lua state
cPluginLua * Plugin = cManualBindings::GetLuaPlugin(tolua_S);
if (Plugin == nullptr)
{
// Warning message has already been printed by GetLuaPlugin(), bail out silently
return 0;
}
// Get the parameters - self and the function reference:
OBJTYPE * self = reinterpret_cast<OBJTYPE *>(tolua_tousertype(tolua_S, 1, nullptr));
if (self == nullptr)
cLuaState L(tolua_S);
OBJTYPE * self;
cLuaState::cCallbackPtr callback;
if (!L.GetStackValues(1, self, callback))
{
LOGWARNING("%s: invalid self (%p)", __FUNCTION__, static_cast<void *>(self));
return 0;
}
int FnRef = luaL_ref(tolua_S, LUA_REGISTRYINDEX); // Store function reference for later retrieval
if (FnRef == LUA_REFNIL)
{
LOGERROR("%s: Cannot create a function reference. Callback not set.", __FUNCTION__);
LOGWARNING("%s: Cannot get parameters", __FUNCTION__);
L.LogStackTrace();
return 0;
}
// Set the callback
(self->*SetCallback)(Plugin, FnRef);
(self->*SetCallback)(std::move(callback));
return 0;
}
@ -1702,46 +1692,70 @@ static int tolua_SetObjectCallback(lua_State * tolua_S)
// Callback class used for the WebTab:
class cWebTabCallback:
public cWebAdmin::cWebTabCallback
{
public:
/** The Lua callback to call to generate the page contents. */
cLuaState::cCallback m_Callback;
virtual bool Call(
const HTTPRequest & a_Request,
const AString & a_UrlPath,
AString & a_Content,
AString & a_ContentType
) override
{
AString content, contentType;
return m_Callback.Call(&a_Request, a_UrlPath, cLuaState::Return, a_Content, a_ContentType);
}
};
static int tolua_cPluginLua_AddWebTab(lua_State * tolua_S)
{
cLuaState LuaState(tolua_S);
cPluginLua * self = nullptr;
// OBSOLETE, use cWebAdmin:AddWebTab() instead!
// Function signature:
// cPluginLua:AddWebTab(Title, CallbackFn, [UrlPath])
if (!LuaState.GetStackValue(1, self))
// TODO: Warn about obsolete API usage
// Only implement after merging the new API change and letting some time for changes in the plugins
// Check params:
cLuaState LuaState(tolua_S);
cPluginLua * self = cManualBindings::GetLuaPlugin(tolua_S);
if (self == nullptr)
{
return 0;
}
if (
!LuaState.CheckParamString(2) ||
!LuaState.CheckParamFunction(3) ||
// Optional string as param 4
!LuaState.CheckParamEnd(5)
)
{
LOGWARNING("cPluginLua:AddWebTab: invalid self as first argument");
return 0;
}
tolua_Error tolua_err;
tolua_err.array = 0;
tolua_err.index = 3;
tolua_err.type = "function";
std::string Title;
int Reference = LUA_REFNIL;
if (LuaState.CheckParamString(2) && LuaState.CheckParamFunction(3))
// Read the params:
AString title, urlPath;
auto callback = std::make_shared<cWebTabCallback>();
if (!LuaState.GetStackValues(2, title, callback->m_Callback))
{
Reference = luaL_ref(tolua_S, LUA_REGISTRYINDEX);
LuaState.GetStackValue(2, Title);
LOGWARNING("cPlugin:AddWebTab(): Cannot read required parameters");
return 0;
}
else
if (!LuaState.GetStackValue(4, urlPath))
{
return cManualBindings::tolua_do_error(tolua_S, "#ferror calling function '#funcname#'", &tolua_err);
urlPath = cWebAdmin::GetURLEncodedString(title);
}
if (Reference != LUA_REFNIL)
{
if (!self->AddWebTab(Title.c_str(), tolua_S, Reference))
{
luaL_unref(tolua_S, LUA_REGISTRYINDEX, Reference);
}
}
else
{
LOGWARNING("cPluginLua:AddWebTab: invalid function reference in 2nd argument (Title: \"%s\")", Title.c_str());
}
cRoot::Get()->GetWebAdmin()->AddWebTab(title, urlPath, self->GetName(), callback);
return 0;
}
@ -2107,22 +2121,68 @@ static int tolua_cUrlParser_ParseAuthorityPart(lua_State * a_LuaState)
static int tolua_cWebAdmin_GetPlugins(lua_State * tolua_S)
static int tolua_cWebAdmin_AddWebTab(lua_State * tolua_S)
{
cWebAdmin * self = reinterpret_cast<cWebAdmin *>(tolua_tousertype(tolua_S, 1, nullptr));
// Function signatures:
// cWebAdmin:AddWebTab(Title, UrlPath, CallbackFn)
const cWebAdmin::PluginList & AllPlugins = self->GetPlugins();
// Check params:
cLuaState LuaState(tolua_S);
cPluginLua * self = cManualBindings::GetLuaPlugin(tolua_S);
if (self == nullptr)
{
return 0;
}
if (
// Don't care whether the first param is a cWebAdmin instance or class
!LuaState.CheckParamString(2, 3) ||
!LuaState.CheckParamFunction(4) ||
!LuaState.CheckParamEnd(5)
)
{
return 0;
}
lua_createtable(tolua_S, static_cast<int>(AllPlugins.size()), 0);
// Read the params:
AString title, urlPath;
auto callback = std::make_shared<cWebTabCallback>();
if (!LuaState.GetStackValues(2, title, urlPath, callback->m_Callback))
{
LOGWARNING("cWebAdmin:AddWebTab(): Cannot read required parameters");
return 0;
}
cRoot::Get()->GetWebAdmin()->AddWebTab(title, urlPath, self->GetName(), callback);
return 0;
}
static int tolua_cWebAdmin_GetAllWebTabs(lua_State * tolua_S)
{
// Function signature:
// cWebAdmin:GetAllWebTabs() -> { {"PluginName", "UrlPath", "Title"}, {"PluginName", "UrlPath", "Title"}, ...}
// Don't care about params at all
auto webTabs = cRoot::Get()->GetWebAdmin()->GetAllWebTabs();
lua_createtable(tolua_S, static_cast<int>(webTabs.size()), 0);
int newTable = lua_gettop(tolua_S);
int index = 1;
cWebAdmin::PluginList::const_iterator iter = AllPlugins.begin();
while (iter != AllPlugins.end())
cLuaState L(tolua_S);
for (const auto & wt: webTabs)
{
const cWebPlugin * Plugin = *iter;
tolua_pushusertype(tolua_S, reinterpret_cast<void *>(const_cast<cWebPlugin*>(Plugin)), "const cWebPlugin");
lua_createtable(tolua_S, 0, 3);
L.Push(wt->m_PluginName);
lua_setfield(tolua_S, -2, "PluginName");
L.Push(wt->m_UrlPath);
lua_setfield(tolua_S, -2, "UrlPath");
L.Push(wt->m_Title);
lua_setfield(tolua_S, -2, "Title");
lua_rawseti(tolua_S, newTable, index);
++iter;
++index;
}
return 1;
@ -2132,14 +2192,70 @@ static int tolua_cWebAdmin_GetPlugins(lua_State * tolua_S)
/** Binding for cWebAdmin::GetHTMLEscapedString.
/** Binding for cWebAdmin::GetBaseURL.
Manual code required because ToLua generates an extra return value */
static int tolua_AllToLua_cWebAdmin_GetHTMLEscapedString(lua_State * tolua_S)
static int tolua_cWebAdmin_GetBaseURL(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
!S.CheckParamUserTable(1, "cWebAdmin") ||
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
{
return 0;
}
// Get the parameters:
AString Input;
S.GetStackValue(2, Input);
// Convert and return:
S.Push(cWebAdmin::GetBaseURL(Input));
return 1;
}
/** Binding for cWebAdmin::GetContentTypeFromFileExt.
Manual code required because ToLua generates an extra return value */
static int tolua_cWebAdmin_GetContentTypeFromFileExt(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
{
return 0;
}
// Get the parameters:
AString Input;
S.GetStackValue(2, Input);
// Convert and return:
S.Push(cWebAdmin::GetContentTypeFromFileExt(Input));
return 1;
}
/** Binding for cWebAdmin::GetHTMLEscapedString.
Manual code required because ToLua generates an extra return value */
static int tolua_cWebAdmin_GetHTMLEscapedString(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
@ -2160,14 +2276,71 @@ static int tolua_AllToLua_cWebAdmin_GetHTMLEscapedString(lua_State * tolua_S)
/** Binding for cWebAdmin::GetPage. */
static int tolua_cWebAdmin_GetPage(lua_State * tolua_S)
{
/*
Function signature:
cWebAdmin:GetPage(a_HTTPRequest) ->
{
Content = "", // Content generated by the plugin
ContentType = "", // Content type generated by the plugin (default: "text/html")
UrlPath = "", // URL path of the tab
TabTitle = "", // Tab's title, as register via cWebAdmin:AddWebTab()
PluginName = "", // Plugin's API name
PluginFolder = "", // Plugin's folder name (display name)
}
*/
// Check the param types:
cLuaState S(tolua_S);
if (
// Don't care about first param, whether it's cWebAdmin instance or class
!S.CheckParamUserType(2, "HTTPRequest") ||
!S.CheckParamEnd(3)
)
{
return 0;
}
// Get the parameters:
HTTPRequest * request = nullptr;
if (!S.GetStackValue(2, request))
{
LOGWARNING("cWebAdmin:GetPage(): Cannot read the HTTPRequest parameter.");
return 0;
}
// Generate the page and push the results as a dictionary-table:
auto page = cRoot::Get()->GetWebAdmin()->GetPage(*request);
lua_createtable(S, 0, 6);
S.Push(page.Content);
lua_setfield(S, -2, "Content");
S.Push(page.ContentType);
lua_setfield(S, -2, "ContentType");
S.Push(page.TabUrlPath);
lua_setfield(S, -2, "UrlPath");
S.Push(page.TabTitle);
lua_setfield(S, -2, "TabTitle");
S.Push(page.PluginName);
lua_setfield(S, -2, "PluginName");
S.Push(cPluginManager::Get()->GetPluginFolderName(page.PluginName));
lua_setfield(S, -2, "PluginFolder");
return 1;
}
/** Binding for cWebAdmin::GetURLEncodedString.
Manual code required because ToLua generates an extra return value */
static int tolua_AllToLua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
static int tolua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
!S.CheckParamUserTable(1, "cWebAdmin") ||
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
@ -2188,27 +2361,6 @@ static int tolua_AllToLua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
static int tolua_cWebPlugin_GetTabNames(lua_State * tolua_S)
{
// Returns a map of (SafeTitle -> Title) for the plugin's web tabs.
auto self = reinterpret_cast<cWebPlugin *>(tolua_tousertype(tolua_S, 1, nullptr));
auto TabNames = self->GetTabNames();
lua_newtable(tolua_S);
int index = 1;
for (auto itr = TabNames.cbegin(), end = TabNames.cend(); itr != end; ++itr)
{
tolua_pushstring(tolua_S, itr->second.c_str()); // Because the SafeTitle is supposed to be unique, use it as key
tolua_pushstring(tolua_S, itr->first.c_str());
lua_rawset(tolua_S, -3);
++index;
}
return 1;
}
static int tolua_cClientHandle_SendPluginMessage(lua_State * L)
{
cLuaState S(L);
@ -2624,6 +2776,79 @@ static int tolua_cLineBlockTracer_Trace(lua_State * tolua_S)
static int tolua_cLuaWindow_new(lua_State * tolua_S)
{
// Function signature:
// cLuaWindow:new(type, slotsX, slotsY, title)
// Check params:
cLuaState L(tolua_S);
if (
!L.CheckParamUserTable(1, "cLuaWindow") ||
!L.CheckParamNumber(2, 4) ||
!L.CheckParamString(5) ||
!L.CheckParamEnd(6)
)
{
return 0;
}
// Read params:
int windowType, slotsX, slotsY;
AString title;
if (!L.GetStackValues(2, windowType, slotsX, slotsY, title))
{
LOGWARNING("%s: Cannot read Lua parameters", __FUNCTION__);
L.LogStackValues();
L.LogStackTrace();
}
// Create the window and return it:
L.Push(new cLuaWindow(L, static_cast<cLuaWindow::WindowType>(windowType), slotsX, slotsY, title));
return 1;
}
static int tolua_cLuaWindow_new_local(lua_State * tolua_S)
{
// Function signature:
// cLuaWindow:new(type, slotsX, slotsY, title)
// Check params:
cLuaState L(tolua_S);
if (
!L.CheckParamUserTable(1, "cLuaWindow") ||
!L.CheckParamNumber(2, 4) ||
!L.CheckParamString(5) ||
!L.CheckParamEnd(6)
)
{
return 0;
}
// Read params:
int windowType, slotsX, slotsY;
AString title;
if (!L.GetStackValues(2, windowType, slotsX, slotsY, title))
{
LOGWARNING("%s: Cannot read Lua parameters", __FUNCTION__);
L.LogStackValues();
L.LogStackTrace();
}
// Create the window, register it for GC and return it:
L.Push(new cLuaWindow(L, static_cast<cLuaWindow::WindowType>(windowType), slotsX, slotsY, title));
tolua_register_gc(tolua_S, lua_gettop(tolua_S));
return 1;
}
static int tolua_cRoot_GetBuildCommitID(lua_State * tolua_S)
{
cLuaState L(tolua_S);
@ -3167,7 +3392,7 @@ static int tolua_cBoundingBox_CalcLineIntersection(lua_State * a_LuaState)
const cBoundingBox * bbox;
if (!L.GetStackValues(1, bbox, pt1, pt2)) // Try the regular signature
{
L.LogStack();
L.LogStackValues();
tolua_error(a_LuaState, "Invalid function params. Expected either bbox:CalcLineIntersection(pt1, pt2) or cBoundingBox:CalcLineIntersection(min, max, pt1, pt2).", nullptr);
return 0;
}
@ -3197,7 +3422,7 @@ static int tolua_cBoundingBox_Intersect(lua_State * a_LuaState)
const cBoundingBox * other;
if (!L.GetStackValues(1, self, other))
{
L.LogStack();
L.LogStackValues();
tolua_error(a_LuaState, "Invalid function params. Expected bbox:Intersect(otherBbox).", nullptr);
return 0;
}
@ -3570,6 +3795,9 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cLuaWindow");
tolua_function(tolua_S, "new", tolua_cLuaWindow_new);
tolua_function(tolua_S, "new_local", tolua_cLuaWindow_new_local);
tolua_function(tolua_S, ".call", tolua_cLuaWindow_new_local);
tolua_function(tolua_S, "SetOnClosing", tolua_SetObjectCallback<cLuaWindow, &cLuaWindow::SetOnClosing>);
tolua_function(tolua_S, "SetOnSlotChanged", tolua_SetObjectCallback<cLuaWindow, &cLuaWindow::SetOnSlotChanged>);
tolua_endmodule(tolua_S);
@ -3590,7 +3818,6 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_beginmodule(tolua_S, "cPlayer");
tolua_function(tolua_S, "GetPermissions", tolua_cPlayer_GetPermissions);
tolua_function(tolua_S, "GetRestrictions", tolua_cPlayer_GetRestrictions);
tolua_function(tolua_S, "OpenWindow", tolua_cPlayer_OpenWindow);
tolua_function(tolua_S, "PermissionMatches", tolua_cPlayer_PermissionMatches);
tolua_endmodule(tolua_S);
@ -3655,13 +3882,13 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cWebAdmin");
tolua_function(tolua_S, "GetHTMLEscapedString", tolua_AllToLua_cWebAdmin_GetHTMLEscapedString);
tolua_function(tolua_S, "GetPlugins", tolua_cWebAdmin_GetPlugins);
tolua_function(tolua_S, "GetURLEncodedString", tolua_AllToLua_cWebAdmin_GetURLEncodedString);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cWebPlugin");
tolua_function(tolua_S, "GetTabNames", tolua_cWebPlugin_GetTabNames);
tolua_function(tolua_S, "AddWebTab", tolua_cWebAdmin_AddWebTab);
tolua_function(tolua_S, "GetAllWebTabs", tolua_cWebAdmin_GetAllWebTabs);
tolua_function(tolua_S, "GetBaseURL", tolua_cWebAdmin_GetBaseURL);
tolua_function(tolua_S, "GetContentTypeFromFileExt", tolua_cWebAdmin_GetContentTypeFromFileExt);
tolua_function(tolua_S, "GetHTMLEscapedString", tolua_cWebAdmin_GetHTMLEscapedString);
tolua_function(tolua_S, "GetPage", tolua_cWebAdmin_GetPage);
tolua_function(tolua_S, "GetURLEncodedString", tolua_cWebAdmin_GetURLEncodedString);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "HTTPRequest");

View File

@ -103,7 +103,9 @@ static int tolua_cWorld_ChunkStay(lua_State * tolua_S)
return 0;
}
ChunkStay->Enable(*World->GetChunkMap(), 3, 4);
cLuaState::cCallbackPtr onChunkAvailable, onAllChunksAvailable;
L.GetStackValues(3, onChunkAvailable, onAllChunksAvailable); // Callbacks may be unassigned at all - as a request to load / generate chunks
ChunkStay->Enable(*World->GetChunkMap(), std::move(onChunkAvailable), std::move(onAllChunksAvailable));
return 0;
}
@ -466,67 +468,41 @@ static int tolua_cWorld_PrepareChunk(lua_State * tolua_S)
class cLuaWorldTask :
public cPluginLua::cResettable
{
public:
cLuaWorldTask(cPluginLua & a_Plugin, int a_FnRef) :
cPluginLua::cResettable(a_Plugin),
m_FnRef(a_FnRef)
{
}
void Run(cWorld & a_World)
{
cCSLock Lock(m_CSPlugin);
if (m_Plugin != nullptr)
{
m_Plugin->Call(m_FnRef, &a_World);
}
}
protected:
int m_FnRef;
};
static int tolua_cWorld_QueueTask(lua_State * tolua_S)
{
// Binding for cWorld::QueueTask
// Params: function
// Retrieve the cPlugin from the LuaState:
cPluginLua * Plugin = cManualBindings::GetLuaPlugin(tolua_S);
if (Plugin == nullptr)
{
// An error message has been already printed in GetLuaPlugin()
return 0;
}
// Function signature:
// World:QueueTask(Callback)
// Retrieve the args:
cWorld * self = reinterpret_cast<cWorld *>(tolua_tousertype(tolua_S, 1, nullptr));
if (self == nullptr)
cLuaState L(tolua_S);
if (
!L.CheckParamUserType(1, "cWorld") ||
!L.CheckParamNumber (2) ||
!L.CheckParamFunction(3)
)
{
return 0;
}
cWorld * World;
cLuaState::cCallbackSharedPtr Task;
if (!L.GetStackValues(1, World, Task))
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Cannot read parameters");
}
if (World == nullptr)
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance");
}
if (!lua_isfunction(tolua_S, 2))
if (!Task->IsValid())
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #1");
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Could not store the callback parameter");
}
// Create a reference to the function:
int FnRef = luaL_ref(tolua_S, LUA_REGISTRYINDEX);
if (FnRef == LUA_REFNIL)
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Could not get function reference of parameter #1");
}
auto ResettableTask = std::make_shared<cLuaWorldTask>(*Plugin, FnRef);
Plugin->AddResettable(ResettableTask);
self->QueueTask(std::bind(&cLuaWorldTask::Run, ResettableTask, std::placeholders::_1));
World->QueueTask([Task](cWorld & a_World)
{
Task->Call(&a_World);
}
);
return 0;
}
@ -576,16 +552,8 @@ static int tolua_cWorld_SetSignLines(lua_State * tolua_S)
static int tolua_cWorld_ScheduleTask(lua_State * tolua_S)
{
// Binding for cWorld::ScheduleTask
// Params: function, Ticks
// Retrieve the cPlugin from the LuaState:
cPluginLua * Plugin = cManualBindings::GetLuaPlugin(tolua_S);
if (Plugin == nullptr)
{
// An error message has been already printed in GetLuaPlugin()
return 0;
}
// Function signature:
// World:ScheduleTask(NumTicks, Callback)
// Retrieve the args:
cLuaState L(tolua_S);
@ -597,22 +565,27 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S)
{
return 0;
}
cWorld * World = reinterpret_cast<cWorld *>(tolua_tousertype(tolua_S, 1, nullptr));
cWorld * World;
int NumTicks;
auto Task = std::make_shared<cLuaState::cCallback>();
if (!L.GetStackValues(1, World, NumTicks, Task))
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Cannot read parameters");
}
if (World == nullptr)
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance");
}
// Create a reference to the function:
int FnRef = luaL_ref(tolua_S, LUA_REGISTRYINDEX);
if (FnRef == LUA_REFNIL)
if (!Task->IsValid())
{
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Could not get function reference of parameter #1");
return cManualBindings::lua_do_error(tolua_S, "Error in function call '#funcname#': Could not store the callback parameter");
}
auto ResettableTask = std::make_shared<cLuaWorldTask>(*Plugin, FnRef);
Plugin->AddResettable(ResettableTask);
World->ScheduleTask(static_cast<int>(tolua_tonumber(tolua_S, 2, 0)), std::bind(&cLuaWorldTask::Run, ResettableTask, std::placeholders::_1));
World->ScheduleTask(NumTicks, [Task](cWorld & a_World)
{
Task->Call(&a_World);
}
);
return 0;
}

View File

@ -110,21 +110,6 @@ public:
virtual bool OnWorldStarted (cWorld & a_World) = 0;
virtual bool OnWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec) = 0;
/** Handles the command split into a_Split, issued by player a_Player.
Command permissions have already been checked.
Returns true if command handled successfully. */
virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) = 0;
/** Handles the console command split into a_Split.
Returns true if command handled successfully. Output is to be sent to the a_Output callback. */
virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) = 0;
/** All bound commands are to be removed, do any language-dependent cleanup here */
virtual void ClearCommands(void) {}
/** All bound console commands are to be removed, do any language-dependent cleanup here */
virtual void ClearConsoleCommands(void) {}
// tolua_begin
const AString & GetName(void) const { return m_Name; }
void SetName(const AString & a_Name) { m_Name = a_Name; }

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@
#pragma once
#include "Plugin.h"
#include "WebPlugin.h"
#include "LuaState.h"
// Names for the global variables through which the plugin is identified in its LuaState
@ -29,8 +28,7 @@ class cWindow;
// tolua_begin
class cPluginLua :
public cPlugin,
public cWebPlugin
public cPlugin
{
typedef cPlugin super;
@ -49,7 +47,7 @@ public:
public:
cOperation(cPluginLua & a_Plugin) :
m_Plugin(a_Plugin),
m_Lock(a_Plugin.m_CriticalSection)
m_Lock(a_Plugin.m_LuaState)
{
}
@ -58,42 +56,12 @@ public:
protected:
cPluginLua & m_Plugin;
/** RAII lock for m_Plugin.m_CriticalSection */
cCSLock m_Lock;
/** RAII lock for the Lua state. */
cLuaState::cLock m_Lock;
} ;
/** A base class that represents something related to a plugin
The plugin can reset this class so that the instance can continue to exist but will not engage the (possibly non-existent) plugin anymore.
This is used for scheduled tasks etc., so that they can be queued and reset when the plugin is terminated, without removing them from the queue. */
class cResettable
{
public:
/** Creates a new instance bound to the specified plugin. */
cResettable(cPluginLua & a_Plugin);
// Force a virtual destructor in descendants:
virtual ~cResettable() {}
/** Resets the plugin instance stored within.
The instance will continue to exist, but should not call into the plugin anymore. */
virtual void Reset(void);
protected:
/** The plugin that this instance references.
If nullptr, the plugin has already unloaded and the instance should bail out any processing.
Protected against multithreaded access by m_CSPlugin. */
cPluginLua * m_Plugin;
/** The mutex protecting m_Plugin against multithreaded access. */
cCriticalSection m_CSPlugin;
};
typedef SharedPtr<cResettable> cResettablePtr;
typedef std::vector<cResettablePtr> cResettablePtrs;
cPluginLua(const AString & a_PluginDirectory);
~cPluginLua();
@ -170,52 +138,15 @@ public:
virtual bool OnWorldStarted (cWorld & a_World) override;
virtual bool OnWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec) override;
virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) override;
virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) override;
virtual void ClearCommands(void) override;
virtual void ClearConsoleCommands(void) override;
/** Returns true if the plugin contains the function for the specified hook type, using the old-style registration (#121) */
bool CanAddOldStyleHook(int a_HookType);
// cWebPlugin overrides
virtual const AString GetWebTitle(void) const override {return GetName(); }
virtual AString HandleWebRequest(const HTTPRequest & a_Request) override;
/** Adds a new web tab to webadmin.
Displaying the tab calls the referenced function. */
bool AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference); // Exported in ManualBindings.cpp
/** Binds the command to call the function specified by a Lua function reference. Simply adds to CommandMap. */
void BindCommand(const AString & a_Command, int a_FnRef);
/** Binds the console command to call the function specified by a Lua function reference. Simply adds to CommandMap. */
void BindConsoleCommand(const AString & a_Command, int a_FnRef);
cLuaState & GetLuaState(void) { return m_LuaState; }
cCriticalSection & GetCriticalSection(void) { return m_CriticalSection; }
/** Removes a previously referenced object (luaL_unref()) */
void Unreference(int a_LuaRef);
/** Calls the plugin-specified "cLuaWindow closing" callback. Returns true only if the callback returned true */
bool CallbackWindowClosing(int a_FnRef, cWindow & a_Window, cPlayer & a_Player, bool a_CanRefuse);
/** Calls the plugin-specified "cLuaWindow slot changed" callback. */
void CallbackWindowSlotChanged(int a_FnRef, cWindow & a_Window, int a_SlotNum);
/** Returns the name of Lua function that should handle the specified hook type in the older (#121) API */
static const char * GetHookFnName(int a_HookType);
/** Adds a Lua function to be called for the specified hook.
The function has to be on the Lua stack at the specified index a_FnRefIdx
Returns true if the hook was added successfully.
*/
bool AddHookRef(int a_HookType, int a_FnRefIdx);
/** Adds a Lua callback to be called for the specified hook.
Returns true if the hook was added successfully. */
bool AddHookCallback(int a_HookType, cLuaState::cCallbackPtr && a_Callback);
/** Calls a function in this plugin's LuaState with parameters copied over from a_ForeignState.
The values that the function returns are placed onto a_ForeignState.
@ -231,45 +162,50 @@ public:
template <typename FnT, typename... Args>
bool Call(FnT a_Fn, Args && ... a_Args)
{
cCSLock Lock(m_CriticalSection);
return m_LuaState.Call(a_Fn, a_Args...);
return cOperation(*this)().Call(a_Fn, a_Args...);
}
/** Adds the specified cResettable instance to m_Resettables, so that it is notified when the plugin is being closed. */
void AddResettable(cResettablePtr a_Resettable);
protected:
/** Maps command name into Lua function reference */
typedef std::map<AString, int> CommandMap;
/** Provides an array of Lua function references */
typedef std::vector<cLuaState::cRef *> cLuaRefs;
typedef std::vector<cLuaState::cCallbackPtr> cLuaCallbacks;
/** Maps hook types into arrays of Lua function references to call for each hook type */
typedef std::map<int, cLuaRefs> cHookMap;
typedef std::map<int, cLuaCallbacks> cHookMap;
/** The mutex protecting m_LuaState and each of the m_Resettables[] against multithreaded use. */
cCriticalSection m_CriticalSection;
/** The plugin's Lua state. */
cLuaState m_LuaState;
/** Objects that need notification when the plugin is about to be unloaded. */
cResettablePtrs m_Resettables;
/** In-game commands that the plugin has registered. */
CommandMap m_Commands;
/** Console commands that the plugin has registered. */
CommandMap m_ConsoleCommands;
/** Hooks that the plugin has registered. */
cHookMap m_HookMap;
/** Releases all Lua references, notifies and removes all m_Resettables[] and closes the m_LuaState. */
void Close(void);
/** Removes all WebTabs currently registered for this plugin from the WebAdmin. */
void ClearWebTabs(void);
/** Calls a hook that has the simple format - single bool return value specifying whether the chain should continue.
The advanced hook types that need more processing implement a similar loop manually instead.
Returns true if any of hook calls wants to abort the hook (returned true), false if all hook calls returned false. */
template <typename... Args>
bool CallSimpleHooks(int a_HookType, Args && ... a_Args)
{
cOperation op(*this);
auto & hooks = m_HookMap[a_HookType];
bool res = false;
for (auto & hook: hooks)
{
hook->Call(std::forward<Args>(a_Args)..., cLuaState::Return, res);
if (res)
{
// Hook wants to terminate the chain processing
return true;
}
}
return false;
}
} ; // tolua_export

View File

@ -1569,9 +1569,9 @@ cPluginManager::CommandResult cPluginManager::HandleCommand(cPlayer & a_Player,
return crNoPermission;
}
ASSERT(cmd->second.m_Plugin != nullptr);
ASSERT(cmd->second.m_Handler != nullptr);
if (!cmd->second.m_Plugin->HandleCommand(Split, a_Player, a_Command))
if (!cmd->second.m_Handler->ExecuteCommand(Split, &a_Player, a_Command, nullptr))
{
return crError;
}
@ -1654,11 +1654,6 @@ void cPluginManager::RemoveHooks(cPlugin * a_Plugin)
void cPluginManager::RemovePluginCommands(cPlugin * a_Plugin)
{
if (a_Plugin != nullptr)
{
a_Plugin->ClearCommands();
}
for (CommandMap::iterator itr = m_Commands.begin(); itr != m_Commands.end();)
{
if (itr->second.m_Plugin == a_Plugin)
@ -1694,7 +1689,13 @@ bool cPluginManager::IsPluginLoaded(const AString & a_PluginName)
bool cPluginManager::BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString)
bool cPluginManager::BindCommand(
const AString & a_Command,
cPlugin * a_Plugin,
cCommandHandlerPtr a_Handler,
const AString & a_Permission,
const AString & a_HelpString
)
{
CommandMap::iterator cmd = m_Commands.find(a_Command);
if (cmd != m_Commands.end())
@ -1703,9 +1704,11 @@ bool cPluginManager::BindCommand(const AString & a_Command, cPlugin * a_Plugin,
return false;
}
m_Commands[a_Command].m_Plugin = a_Plugin;
m_Commands[a_Command].m_Permission = a_Permission;
m_Commands[a_Command].m_HelpString = a_HelpString;
auto & reg = m_Commands[a_Command];
reg.m_Plugin = a_Plugin;
reg.m_Handler = a_Handler;
reg.m_Permission = a_Permission;
reg.m_HelpString = a_HelpString;
return true;
}
@ -1768,11 +1771,6 @@ cPluginManager::CommandResult cPluginManager::ForceExecuteCommand(cPlayer & a_Pl
void cPluginManager::RemovePluginConsoleCommands(cPlugin * a_Plugin)
{
if (a_Plugin != nullptr)
{
a_Plugin->ClearConsoleCommands();
}
for (CommandMap::iterator itr = m_ConsoleCommands.begin(); itr != m_ConsoleCommands.end();)
{
if (itr->second.m_Plugin == a_Plugin)
@ -1792,7 +1790,12 @@ void cPluginManager::RemovePluginConsoleCommands(cPlugin * a_Plugin)
bool cPluginManager::BindConsoleCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_HelpString)
bool cPluginManager::BindConsoleCommand(
const AString & a_Command,
cPlugin * a_Plugin,
cCommandHandlerPtr a_Handler,
const AString & a_HelpString
)
{
CommandMap::iterator cmd = m_ConsoleCommands.find(a_Command);
if (cmd != m_ConsoleCommands.end())
@ -1808,9 +1811,11 @@ bool cPluginManager::BindConsoleCommand(const AString & a_Command, cPlugin * a_P
return false;
}
m_ConsoleCommands[a_Command].m_Plugin = a_Plugin;
m_ConsoleCommands[a_Command].m_Permission = "";
m_ConsoleCommands[a_Command].m_HelpString = a_HelpString;
auto & reg = m_ConsoleCommands[a_Command];
reg.m_Plugin = a_Plugin;
reg.m_Handler = a_Handler;
reg.m_Permission = "";
reg.m_HelpString = a_HelpString;
return true;
}
@ -1873,7 +1878,7 @@ bool cPluginManager::ExecuteConsoleCommand(const AStringVector & a_Split, cComma
return (res == crExecuted);
}
return cmd->second.m_Plugin->HandleConsoleCommand(a_Split, a_Output, a_Command);
return cmd->second.m_Handler->ExecuteCommand(a_Split, nullptr, a_Command, &a_Output);
}
@ -1965,6 +1970,23 @@ bool cPluginManager::ForEachPlugin(cPluginCallback & a_Callback)
AString cPluginManager::GetPluginFolderName(const AString & a_PluginName)
{
// TODO: Implement locking for plugins
for (auto & plugin: m_Plugins)
{
if (plugin->GetName() == a_PluginName)
{
return plugin->GetFolderName();
}
}
return AString();
}
void cPluginManager::AddHook(cPlugin * a_Plugin, int a_Hook)
{
if (a_Plugin == nullptr)

View File

@ -152,6 +152,7 @@ public:
HOOK_MAX = HOOK_NUM_HOOKS - 1,
} ; // tolua_export
/** Used as a callback for enumerating bound commands */
class cCommandEnumCallback
{
@ -164,6 +165,30 @@ public:
virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) = 0;
} ;
/** Interface that must be provided by any class that implements a command handler, either in-game or console command. */
class cCommandHandler
{
public:
// Force a virtual destructor in descendants
virtual ~cCommandHandler() {}
/** Executes the specified in-game command.
a_Split is the command string, split at the spaces.
a_Player is the player executing the command, nullptr in case of the console.
a_Command is the entire command string.
a_Output is the sink into which the additional text returned by the command handler should be sent; only used for console commands. */
virtual bool ExecuteCommand(
const AStringVector & a_Split,
cPlayer * a_Player,
const AString & a_Command,
cCommandOutputCallback * a_Output = nullptr
) = 0;
};
typedef SharedPtr<cCommandHandler> cCommandHandlerPtr;
/** The interface used for enumerating and extern-calling plugins */
typedef cItemCallback<cPlugin> cPluginCallback;
@ -281,8 +306,16 @@ public:
/** Returns true if the specified plugin is loaded. */
bool IsPluginLoaded(const AString & a_PluginName); // tolua_export
/** Binds a command to the specified plugin. Returns true if successful, false if command already bound. */
bool BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString); // Exported in ManualBindings.cpp, without the a_Plugin param
/** Binds a command to the specified handler.
Returns true if successful, false if command already bound.
Exported in ManualBindings.cpp. */
bool BindCommand(
const AString & a_Command,
cPlugin * a_Plugin,
cCommandHandlerPtr a_Handler,
const AString & a_Permission,
const AString & a_HelpString
);
/** Calls a_Callback for each bound command, returns true if all commands were enumerated */
bool ForEachCommand(cCommandEnumCallback & a_Callback); // Exported in ManualBindings.cpp
@ -302,8 +335,15 @@ public:
/** Removes all console command bindings that the specified plugin has made */
void RemovePluginConsoleCommands(cPlugin * a_Plugin);
/** Binds a console command to the specified plugin. Returns true if successful, false if command already bound. */
bool BindConsoleCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_HelpString); // Exported in ManualBindings.cpp, without the a_Plugin param
/** Binds a console command to the specified handler.
Returns true if successful, false if command already bound.
Exported in ManualBindings.cpp. */
bool BindConsoleCommand(
const AString & a_Command,
cPlugin * a_Plugin,
cCommandHandlerPtr a_Handler,
const AString & a_HelpString
);
/** Calls a_Callback for each bound console command, returns true if all commands were enumerated */
bool ForEachConsoleCommand(cCommandEnumCallback & a_Callback); // Exported in ManualBindings.cpp
@ -332,6 +372,9 @@ public:
Returns true if all plugins have been reported, false if the callback has aborted the enumeration by returning true. */
bool ForEachPlugin(cPluginCallback & a_Callback);
/** Returns the name of the folder (cPlugin::GetFolderName()) from which the specified plugin was loaded. */
AString GetPluginFolderName(const AString & a_PluginName); // tolua_export
/** Returns the path where individual plugins' folders are expected.
The path doesn't end in a slash. */
static AString GetPluginsPath(void) { return FILE_IO_PREFIX + AString("Plugins"); } // tolua_export
@ -345,6 +388,7 @@ private:
cPlugin * m_Plugin;
AString m_Permission; // Not used for console commands
AString m_HelpString;
cCommandHandlerPtr m_Handler;
} ;
typedef std::map<int, cPluginManager::PluginList> HookMap;

View File

@ -1,152 +0,0 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "WebPlugin.h"
#include "../WebAdmin.h"
#include "../Root.h"
cWebPlugin::cWebPlugin()
{
cWebAdmin * WebAdmin = cRoot::Get()->GetWebAdmin();
if (WebAdmin != nullptr)
{
WebAdmin->AddPlugin(this);
}
}
cWebPlugin::~cWebPlugin()
{
ASSERT(m_Tabs.empty()); // Has ClearTabs() been called?
// Remove from WebAdmin:
cWebAdmin * WebAdmin = cRoot::Get()->GetWebAdmin();
if (WebAdmin != nullptr)
{
WebAdmin->RemovePlugin(this);
}
}
cWebPlugin::cTabNames cWebPlugin::GetTabNames(void) const
{
std::list< std::pair<AString, AString>> NameList;
for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
NameList.push_back(std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle));
}
return NameList;
}
cWebPlugin::cTabPtr cWebPlugin::GetTabBySafeTitle(const AString & a_SafeTitle) const
{
cCSLock Lock(m_CSTabs);
for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
if ((*itr)->m_SafeTitle == a_SafeTitle)
{
return *itr;
}
}
return nullptr;
}
std::pair<AString, AString> cWebPlugin::GetTabNameForRequest(const HTTPRequest & a_Request)
{
AStringVector Split = StringSplit(a_Request.Path, "/");
if (Split.empty())
{
return std::make_pair(AString(), AString());
}
cCSLock Lock(m_CSTabs);
cTabPtr Tab;
if (Split.size() > 2) // If we got the tab name, show that page
{
for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
if ((*itr)->m_SafeTitle.compare(Split[2]) == 0) // This is the one!
{
return std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle);
}
}
// Tab name not found, display an "empty" page:
return std::make_pair(AString(), AString());
}
// Show the first tab:
if (!m_Tabs.empty())
{
return std::make_pair(m_Tabs.front()->m_SafeTitle, m_Tabs.front()->m_SafeTitle);
}
// No tabs at all:
return std::make_pair(AString(), AString());
}
AString cWebPlugin::SafeString(const AString & a_String)
{
AString RetVal;
auto len = a_String.size();
RetVal.reserve(len);
for (size_t i = 0; i < len; ++i)
{
char c = a_String[i];
if (c == ' ')
{
c = '_';
}
RetVal.push_back(c);
}
return RetVal;
}
void cWebPlugin::AddNewWebTab(const AString & a_Title, int a_UserData)
{
auto Tab = std::make_shared<cTab>(a_Title, a_UserData);
cCSLock Lock(m_CSTabs);
m_Tabs.push_back(Tab);
}
void cWebPlugin::ClearTabs(void)
{
// Remove the webadmin tabs:
cTabPtrs Tabs;
{
cCSLock Lock(m_CSTabs);
std::swap(Tabs, m_Tabs);
}
}

View File

@ -1,80 +0,0 @@
#pragma once
struct HTTPRequest;
// tolua_begin
class cWebPlugin
{
public:
// tolua_end
struct cTab
{
AString m_Title;
AString m_SafeTitle;
int m_UserData;
cTab(const AString & a_Title, int a_UserData):
m_Title(a_Title),
m_SafeTitle(cWebPlugin::SafeString(a_Title)),
m_UserData(a_UserData)
{
}
};
typedef SharedPtr<cTab> cTabPtr;
typedef std::list<cTabPtr> cTabPtrs;
typedef std::list<std::pair<AString, AString>> cTabNames;
cWebPlugin();
virtual ~cWebPlugin();
// tolua_begin
/** Returns the title of the plugin, as it should be presented in the webadmin's pages tree. */
virtual const AString GetWebTitle(void) const = 0;
/** Sanitizes the input string, replacing spaces with underscores. */
static AString SafeString(const AString & a_String);
// tolua_end
virtual AString HandleWebRequest(const HTTPRequest & a_Request) = 0;
/** Adds a new web tab with the specified contents. */
void AddNewWebTab(const AString & a_Title, int a_UserData);
/** Removes all the tabs. */
void ClearTabs(void);
/** Returns all the tabs that this plugin has registered. */
const cTabPtrs & GetTabs(void) const { return m_Tabs; }
/** Returns all of the tabs that this plugin has registered. */
cTabNames GetTabNames(void) const; // Exported in ManualBindings.cpp
/** Returns the tab that has the specified SafeTitle.
Returns nullptr if no such tab. */
cTabPtr GetTabBySafeTitle(const AString & a_SafeTitle) const;
std::pair<AString, AString> GetTabNameForRequest(const HTTPRequest & a_Request);
private:
/** All tabs that this plugin has registered.
Protected against multithreaded access by m_CSTabs. */
cTabPtrs m_Tabs;
/** Protects m_Tabs against multithreaded access. */
mutable cCriticalSection m_CSTabs;
}; // tolua_export

View File

@ -224,11 +224,11 @@ public:
cWindow * GetWindow(void) { return m_CurrentWindow; } // tolua_export
const cWindow * GetWindow(void) const { return m_CurrentWindow; }
/** Opens the specified window; closes the current one first using CloseWindow() */
void OpenWindow(cWindow * a_Window); // Exported in ManualBindings.cpp
// tolua_begin
/** Opens the specified window; closes the current one first using CloseWindow() */
void OpenWindow(cWindow * a_Window);
/** Closes the current window, resets current window to m_InventoryWindow. A plugin may refuse the closing if a_CanRefuse is true */
void CloseWindow(bool a_CanRefuse = true);

View File

@ -607,18 +607,35 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback &
void cServer::BindBuiltInConsoleCommands(void)
{
// Create an empty handler - the actual handling for the commands is performed before they are handed off to cPluginManager
class cEmptyHandler:
public cPluginManager::cCommandHandler
{
virtual bool ExecuteCommand(
const AStringVector & a_Split,
cPlayer * a_Player,
const AString & a_Command,
cCommandOutputCallback * a_Output = nullptr
) override
{
return false;
}
};
auto handler = std::make_shared<cEmptyHandler>();
// Register internal commands:
cPluginManager * PlgMgr = cPluginManager::Get();
PlgMgr->BindConsoleCommand("help", nullptr, "Shows the available commands");
PlgMgr->BindConsoleCommand("reload", nullptr, "Reloads all plugins");
PlgMgr->BindConsoleCommand("restart", nullptr, "Restarts the server cleanly");
PlgMgr->BindConsoleCommand("stop", nullptr, "Stops the server cleanly");
PlgMgr->BindConsoleCommand("chunkstats", nullptr, "Displays detailed chunk memory statistics");
PlgMgr->BindConsoleCommand("load <pluginname>", nullptr, "Adds and enables the specified plugin");
PlgMgr->BindConsoleCommand("unload <pluginname>", nullptr, "Disables the specified plugin");
PlgMgr->BindConsoleCommand("destroyentities", nullptr, "Destroys all entities in all worlds");
PlgMgr->BindConsoleCommand("help", nullptr, handler, "Shows the available commands");
PlgMgr->BindConsoleCommand("reload", nullptr, handler, "Reloads all plugins");
PlgMgr->BindConsoleCommand("restart", nullptr, handler, "Restarts the server cleanly");
PlgMgr->BindConsoleCommand("stop", nullptr, handler, "Stops the server cleanly");
PlgMgr->BindConsoleCommand("chunkstats", nullptr, handler, "Displays detailed chunk memory statistics");
PlgMgr->BindConsoleCommand("load", nullptr, handler, "Adds and enables the specified plugin");
PlgMgr->BindConsoleCommand("unload", nullptr, handler, "Disables the specified plugin");
PlgMgr->BindConsoleCommand("destroyentities", nullptr, handler, "Destroys all entities in all worlds");
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
PlgMgr->BindConsoleCommand("dumpmem", nullptr, " - Dumps all used memory blocks together with their callstacks into memdump.xml");
PlgMgr->BindConsoleCommand("dumpmem", nullptr, handler, " - Dumps all used memory blocks together with their callstacks into memdump.xml");
#endif
}

View File

@ -307,7 +307,7 @@ bool cWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse)
// Checks whether the player is still holding an item
if (!a_Player.GetDraggingItem().IsEmpty())
{
LOGD("Player holds item! Dropping it...");
LOGD("Player is holding an item while closing their window, dropping it as a pickup...");
a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount);
}

View File

@ -2,10 +2,6 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "WebAdmin.h"
#include "Bindings/WebPlugin.h"
#include "Bindings/PluginManager.h"
#include "Bindings/Plugin.h"
#include "World.h"
#include "Entities/Player.h"
@ -87,9 +83,9 @@ public:
// cWebAdmin:
cWebAdmin::cWebAdmin(void) :
m_TemplateScript("<webadmin_template>"),
m_IsInitialized(false),
m_IsRunning(false),
m_TemplateScript("<webadmin_template>")
m_IsRunning(false)
{
}
@ -106,40 +102,9 @@ cWebAdmin::~cWebAdmin()
void cWebAdmin::AddPlugin(cWebPlugin * a_Plugin)
{
m_Plugins.remove(a_Plugin);
m_Plugins.push_back(a_Plugin);
}
void cWebAdmin::RemovePlugin(cWebPlugin * a_Plugin)
{
m_Plugins.remove(a_Plugin);
}
bool cWebAdmin::Init(void)
{
if (!m_IniFile.ReadFile("webadmin.ini"))
{
LOGWARN("Regenerating webadmin.ini, all settings will be reset");
m_IniFile.AddHeaderComment(" This file controls the webadmin feature of Cuberite");
m_IniFile.AddHeaderComment(" Username format: [User:*username*]");
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
m_IniFile.AddHeaderComment(" [User:admin]");
m_IniFile.AddHeaderComment(" Password=admin");
m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
m_IniFile.WriteFile("webadmin.ini");
}
if (!m_IniFile.GetValueSetB("WebAdmin", "Enabled", true))
if (!LoadIniFile())
{
// WebAdmin is disabled, bail out faking a success
return true;
@ -147,31 +112,7 @@ bool cWebAdmin::Init(void)
LOGD("Initialising WebAdmin...");
// Initialize the WebAdmin template script and load the file
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
{
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin disabled!", FILE_IO_PREFIX "webadmin/template.lua");
m_TemplateScript.Close();
m_HTTPServer.Stop();
return false;
}
// Load the login template, provide a fallback default if not found:
if (!LoadLoginTemplate())
{
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
// Sets the fallback template:
m_LoginTemplate = \
"<h1>Cuberite WebAdmin</h1>" \
"<center>" \
"<form method='get' action='webadmin/'>" \
"<input type='submit' value='Log in'>" \
"</form>" \
"</center>";
}
Reload();
// Read the ports to be used:
// Note that historically the ports were stored in the "Port" and "PortsIPv6" values
@ -224,7 +165,7 @@ void cWebAdmin::Stop(void)
bool cWebAdmin::LoadLoginTemplate(void)
bool cWebAdmin::LoadLoginPage(void)
{
cFile File(FILE_IO_PREFIX "webadmin/login_template.html", cFile::fmRead);
if (!File.IsOpen())
@ -238,7 +179,8 @@ bool cWebAdmin::LoadLoginTemplate(void)
return false;
}
m_LoginTemplate = TemplateContent;
cCSLock Lock(m_CS);
m_LoginPage = TemplateContent;
return true;
}
@ -246,6 +188,89 @@ bool cWebAdmin::LoadLoginTemplate(void)
void cWebAdmin::RemoveAllPluginWebTabs(const AString & a_PluginName)
{
cCSLock lock(m_CS);
m_WebTabs.erase(std::remove_if(m_WebTabs.begin(), m_WebTabs.end(), [=](cWebTabPtr a_CBWebTab)
{
return (a_CBWebTab->m_PluginName == a_PluginName);
}),
m_WebTabs.end()
);
}
void cWebAdmin::Reload(void)
{
cCSLock lock(m_CS);
if (!LoadIniFile())
{
// We are asked to disable the webadmin, cannot do that, so warn the admin:
LOGWARNING(
"WebAdmin was previously enabled and now the settings say to disable it."
" This will not take effect until you restart the server."
);
}
// Initialize the WebAdmin template script and reload the file:
if (m_TemplateScript.IsValid())
{
m_TemplateScript.Close();
}
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
{
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin will not work properly!", FILE_IO_PREFIX "webadmin/template.lua");
m_TemplateScript.Close();
}
// Load the login template, provide a fallback default if not found:
if (!LoadLoginPage())
{
LOGWARN("Could not load WebAdmin login page \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
// Set the fallback:
m_LoginPage = \
"<h1>Cuberite WebAdmin</h1>" \
"<center>" \
"<form method='get' action='webadmin/'>" \
"<input type='submit' value='Log in'>" \
"</form>" \
"</center>";
}
}
bool cWebAdmin::LoadIniFile(void)
{
m_IniFile.Clear();
if (!m_IniFile.ReadFile("webadmin.ini"))
{
LOGWARN("Regenerating webadmin.ini, all settings will be reset");
m_IniFile.AddHeaderComment(" This file controls the webadmin feature of Cuberite");
m_IniFile.AddHeaderComment(" It specifies whether webadmin is enabled, and what logins are allowed. ");
m_IniFile.AddHeaderComment(" Username format: [User:*username*]");
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
m_IniFile.AddHeaderComment(" [User:admin]");
m_IniFile.AddHeaderComment(" Password=admin");
m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
m_IniFile.WriteFile("webadmin.ini");
}
return m_IniFile.GetValueSetB("WebAdmin", "Enabled", true);
}
void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
if (!a_Request.HasAuth())
@ -255,17 +280,20 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT
}
// Check auth:
AString UserPassword = m_IniFile.GetValue("User:" + a_Request.GetAuthUsername(), "Password", "");
if ((UserPassword == "") || (a_Request.GetAuthPassword() != UserPassword))
{
a_Connection.SendNeedAuth("Cuberite WebAdmin - bad username or password");
return;
cCSLock Lock(m_CS);
AString UserPassword = m_IniFile.GetValue("User:" + a_Request.GetAuthUsername(), "Password", "");
if ((UserPassword == "") || (a_Request.GetAuthPassword() != UserPassword))
{
a_Connection.SendNeedAuth("Cuberite WebAdmin - bad username or password");
return;
}
}
// Check if the contents should be wrapped in the template:
auto BareURL = a_Request.GetURLPath();
ASSERT(BareURL.length() > 0);
bool ShouldWrapInTemplate = ((BareURL.length() > 1) && (BareURL[1] != '~'));
bool ShouldWrapInTemplate = (!BareURL.empty() && (BareURL[1] != '~'));
// Retrieve the request data:
auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData());
@ -312,6 +340,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT
// Try to get the template from the Lua template script
if (ShouldWrapInTemplate)
{
cCSLock Lock(m_CS);
if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template))
{
cHTTPOutgoingResponse Resp;
@ -325,59 +354,12 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT
return;
}
AString BaseURL = GetBaseURL(BareURL);
AString Menu;
Template = "{CONTENT}";
AString FoundPlugin;
for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
{
cWebPlugin * WebPlugin = *itr;
std::list< std::pair<AString, AString> > NameList = WebPlugin->GetTabNames();
for (std::list< std::pair<AString, AString> >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names)
{
Menu += "<li><a href='" + BaseURL + WebPlugin->GetWebTitle().c_str() + "/" + (*Names).second + "'>" + (*Names).first + "</a></li>";
}
}
sWebAdminPage Page = GetPage(TemplateRequest.Request);
AString Content = Page.Content;
FoundPlugin = Page.PluginName;
if (!Page.TabName.empty())
{
FoundPlugin += " - " + Page.TabName;
}
if (FoundPlugin.empty()) // Default page
{
Content = GetDefaultPage();
}
int MemUsageKiB = cRoot::GetPhysicalRAMUsage();
if (MemUsageKiB > 0)
{
ReplaceString(Template, "{MEM}", Printf("%.02f", static_cast<double>(MemUsageKiB) / 1024));
ReplaceString(Template, "{MEMKIB}", Printf("%d", MemUsageKiB));
}
else
{
ReplaceString(Template, "{MEM}", "unknown");
ReplaceString(Template, "{MEMKIB}", "unknown");
}
ReplaceString(Template, "{USERNAME}", a_Request.GetAuthUsername());
ReplaceString(Template, "{MENU}", Menu);
ReplaceString(Template, "{PLUGIN_NAME}", FoundPlugin);
ReplaceString(Template, "{CONTENT}", Content);
ReplaceString(Template, "{TITLE}", "Cuberite");
AString NumChunks;
Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(Template.c_str(), Template.length());
// Send the un-decorated page content:
auto page = GetPage(TemplateRequest.Request);
cHTTPOutgoingResponse resp;
resp.SetContentType(page.ContentType);
a_Connection.Send(resp);
a_Connection.Send(page.Content.c_str(), page.Content.length());
a_Connection.FinishResponse();
}
@ -392,7 +374,7 @@ void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPInc
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(m_LoginTemplate);
a_Connection.Send(m_LoginPage);
a_Connection.FinishResponse();
}
@ -406,7 +388,7 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
std::replace(FileURL.begin(), FileURL.end(), '\\', '/');
// Remove all leading backslashes:
if (FileURL[0] == '/')
if (!FileURL.empty() && (FileURL[0] == '/'))
{
size_t FirstCharToRead = FileURL.find_first_not_of('/');
if (FirstCharToRead != AString::npos)
@ -418,8 +400,9 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
// Remove all "../" strings:
ReplaceString(FileURL, "../", "");
bool LoadedSuccessfull = false;
// Read the file contents and guess its mime-type, based on the extension:
AString Content = "<h2>404 Not Found</h2>";
AString ContentType;
AString Path = Printf(FILE_IO_PREFIX "webadmin/files/%s", FileURL.c_str());
if (cFile::IsFile(Path))
{
@ -427,18 +410,17 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
AString FileContent;
if (File.IsOpen() && (File.ReadRestOfFile(FileContent) != -1))
{
LoadedSuccessfull = true;
Content = FileContent;
std::swap(Content, FileContent);
size_t LastPointPosition = Path.find_last_of('.');
if (LastPointPosition != AString::npos)
{
ContentType = GetContentTypeFromFileExt(Path.substr(LastPointPosition + 1));
}
}
}
// Find content type (The currently method is very bad. We should change it later)
AString ContentType = "text/html";
size_t LastPointPosition = Path.find_last_of('.');
if (LoadedSuccessfull && (LastPointPosition != AString::npos) && (LastPointPosition < Path.length()))
if (ContentType.empty())
{
AString FileExtension = Path.substr(LastPointPosition + 1);
ContentType = GetContentTypeFromFileExt(FileExtension);
ContentType = "application/unknown";
}
// Send the response:
@ -456,32 +438,36 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
AString cWebAdmin::GetContentTypeFromFileExt(const AString & a_FileExtension)
{
static bool IsInitialized = false;
static std::map<AString, AString> ContentTypeMap;
static AStringMap ContentTypeMap;
if (!IsInitialized)
{
// Initialize the ContentTypeMap:
ContentTypeMap["png"] = "image/png";
ContentTypeMap["fif"] = "image/fif";
ContentTypeMap["gif"] = "image/gif";
ContentTypeMap["jpeg"] = "image/jpeg";
ContentTypeMap["jpg"] = "image/jpeg";
ContentTypeMap["jpe"] = "image/jpeg";
ContentTypeMap["tiff"] = "image/tiff";
ContentTypeMap["ico"] = "image/ico";
ContentTypeMap["csv"] = "image/comma-separated-values";
ContentTypeMap["css"] = "text/css";
ContentTypeMap["js"] = "text/javascript";
ContentTypeMap["txt"] = "text/plain";
ContentTypeMap["rtx"] = "text/richtext";
ContentTypeMap["xml"] = "text/xml";
ContentTypeMap["png"] = "image/png";
ContentTypeMap["fif"] = "image/fif";
ContentTypeMap["gif"] = "image/gif";
ContentTypeMap["jpeg"] = "image/jpeg";
ContentTypeMap["jpg"] = "image/jpeg";
ContentTypeMap["jpe"] = "image/jpeg";
ContentTypeMap["tiff"] = "image/tiff";
ContentTypeMap["ico"] = "image/ico";
ContentTypeMap["csv"] = "text/csv";
ContentTypeMap["css"] = "text/css";
ContentTypeMap["js"] = "text/javascript";
ContentTypeMap["txt"] = "text/plain";
ContentTypeMap["rtx"] = "text/richtext";
ContentTypeMap["rtf"] = "text/richtext";
ContentTypeMap["xml"] = "text/xml";
ContentTypeMap["html"] = "text/html";
ContentTypeMap["htm"] = "text/html";
ContentTypeMap["xhtml"] = "application/xhtml+xml"; // Not recomended for IE6, but no-one uses that anymore
}
AString FileExtension = StrToLower(a_FileExtension);
if (ContentTypeMap.find(a_FileExtension) == ContentTypeMap.end())
auto itr = ContentTypeMap.find(StrToLower(a_FileExtension));
if (itr == ContentTypeMap.end())
{
return "text/html";
return AString();
}
return ContentTypeMap[FileExtension];
return itr->second;
}
@ -490,77 +476,49 @@ AString cWebAdmin::GetContentTypeFromFileExt(const AString & a_FileExtension)
sWebAdminPage cWebAdmin::GetPage(const HTTPRequest & a_Request)
{
sWebAdminPage Page;
AStringVector Split = StringSplit(a_Request.Path, "/");
sWebAdminPage page;
auto split = StringSplit(a_Request.Path, "/");
// Find the plugin that corresponds to the requested path
AString FoundPlugin;
if (Split.size() > 1)
// If no specific page was requested, return an empty object:
if (split.size() <= 2)
{
for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
return page;
}
// Find the WebTab handler responsible for the request:
cWebTabPtr tab;
{
cCSLock Lock(m_CS);
for (auto & wt: m_WebTabs)
{
if ((*itr)->GetWebTitle() == Split[1])
if (
(wt->m_PluginName == split[1]) &&
(wt->m_UrlPath == split[2])
)
{
Page.Content = (*itr)->HandleWebRequest(a_Request);
cWebPlugin * WebPlugin = *itr;
FoundPlugin = WebPlugin->GetWebTitle();
AString TabName = WebPlugin->GetTabNameForRequest(a_Request).first;
Page.PluginName = FoundPlugin;
Page.TabName = TabName;
tab = wt;
break;
}
}
} // for wt - m_WebTabs[]
}
// Return the page contents
return Page;
}
AString cWebAdmin::GetDefaultPage(void)
{
AString Content;
Content += "<h4>Server Name:</h4>";
Content += "<p>" + AString( cRoot::Get()->GetServer()->GetServerID()) + "</p>";
// Display a list of all plugins:
Content += "<h4>Plugins:</h4><ul>";
struct cPluginCallback:
public cPluginManager::cPluginCallback
// If a WebTab handler was found, call it:
if (tab != nullptr)
{
AString & m_Content;
cPluginCallback(AString & a_Content):
m_Content(a_Content)
page.ContentType = "text/html"; // Default to HTML content type, unless overridden by a plugin
if (!tab->m_Callback->Call(a_Request, split[1], page.Content, page.ContentType))
{
page.Content = GetHTMLEscapedString(Printf(
"WebTab callback for plugin %s, page %s has failed.",
tab->m_PluginName.c_str(), tab->m_Title.c_str()
));
}
virtual bool Item(cPlugin * a_Plugin) override
{
if (a_Plugin->IsLoaded())
{
AppendPrintf(m_Content, "<li>%s V.%i</li>", a_Plugin->GetName().c_str(), a_Plugin->GetVersion());
}
return false;
}
} Callback(Content);
cPluginManager::Get()->ForEachPlugin(Callback);
Content += "</ul>";
// Display a list of all players:
Content += "<h4>Players:</h4><ul>";
cPlayerAccum PlayerAccum;
cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
if (World != nullptr)
{
World->ForEachPlayer(PlayerAccum);
Content.append(PlayerAccum.m_Contents);
page.PluginName = tab->m_PluginName;
page.TabTitle = tab->m_Title;
page.TabUrlPath = split[1];
}
Content += "</ul><br>";
return Content;
return page;
}
@ -576,6 +534,41 @@ AString cWebAdmin::GetBaseURL(const AString & a_URL)
void cWebAdmin::AddWebTab(
const AString & a_Title,
const AString & a_UrlPath,
const AString & a_PluginName,
SharedPtr<cWebAdmin::cWebTabCallback> a_Callback
)
{
cCSLock lock(m_CS);
m_WebTabs.emplace_back(std::make_shared<cWebTab>(a_Title, a_UrlPath, a_PluginName, a_Callback));
}
bool cWebAdmin::DelWebTab(const AString & a_UrlPath)
{
cCSLock lock(m_CS);
for (auto itr = m_WebTabs.begin(), end = m_WebTabs.end(); itr != end; ++itr)
{
if ((*itr)->m_UrlPath == a_UrlPath)
{
m_WebTabs.erase(itr);
return true;
}
} // for itr - m_WebTabs[]
// Not found:
return false;
}
AString cWebAdmin::GetHTMLEscapedString(const AString & a_Input)
{
AString dst;

View File

@ -89,14 +89,14 @@ struct HTTPTemplateRequest
// tolua_begin
struct sWebAdminPage
{
AString Content;
AString PluginName;
AString TabName;
AString TabTitle;
AString TabUrlPath;
AString ContentType;
};
// tolua_end
@ -111,7 +111,49 @@ class cWebAdmin :
public:
// tolua_end
typedef std::list< cWebPlugin* > PluginList;
/** Interface for getting the content of a single WebTab. */
class cWebTabCallback abstract
{
public:
// Force a virtual destructor in descendants
virtual ~cWebTabCallback() {}
/** Returns the contents for the specified request.
Returns true if the call was successful, false on an error.
a_Request is the full HTTP request object, as received from the client.
a_UrlPath is the UrlPath of the WebTab registered for this request, as parsed from a_Request.
Descendants should fill a_Content with the page contents
and optionally set a_ContentType [defaults to "text/html"] */
virtual bool Call(
const HTTPRequest & a_Request,
const AString & a_UrlPath,
AString & a_Content,
AString & a_ContentType
) = 0;
};
/** Container for a single web tab.
Each web tab has a title, URL path and an associated plugin's name.
Each web tab is registered with a callback to provide the content. */
class cWebTab
{
public:
AString m_Title;
AString m_UrlPath;
AString m_PluginName;
SharedPtr<cWebTabCallback> m_Callback;
cWebTab(const AString & a_Title, const AString & a_UrlPath, const AString & a_PluginName, SharedPtr<cWebTabCallback> a_Callback):
m_Title(a_Title),
m_UrlPath(a_UrlPath),
m_PluginName(a_PluginName),
m_Callback(a_Callback)
{
}
};
typedef SharedPtr<cWebTab> cWebTabPtr;
typedef std::vector<cWebTabPtr> cWebTabPtrs;
cWebAdmin(void);
@ -120,81 +162,115 @@ public:
/** Initializes the object. Returns true if successfully initialized and ready to start */
bool Init(void);
/** Starts the HTTP server taking care of the admin. Returns true if successful */
/** Starts the HTTP server taking care of the webadmin. Returns true if successful */
bool Start(void);
/** Stops the HTTP server, if it was started. */
void Stop(void);
/** Loads the login template. Returns true if the loading succeeds, false if not. */
bool LoadLoginTemplate(void);
/** Loads the login template into m_LoginPage.
Returns true if the loading succeeds, false if not. */
bool LoadLoginPage(void);
void AddPlugin(cWebPlugin * a_Plugin);
void RemovePlugin(cWebPlugin * a_Plugin);
/** Returns a copy of all the registered web tabs.
Exported to Lua in ManualBindings.cpp. */
cWebTabPtrs GetAllWebTabs(void) { return m_WebTabs; }
// TODO: Convert this to the auto-locking callback mechanism used for looping players in worlds and such
PluginList GetPlugins() const { return m_Plugins; } // >> EXPORTED IN MANUALBINDINGS <<
/** Removes all WebTabs registered by the specified plugin. */
void RemoveAllPluginWebTabs(const AString & a_PluginName);
/** Returns the (inner) page contents for the specified request.
Calls the appropriate WebTab handler to get the contents.
Exported to Lua in ManualBindings.cpp. */
sWebAdminPage GetPage(const HTTPRequest & a_Request);
// tolua_begin
sWebAdminPage GetPage(const HTTPRequest & a_Request);
/** Reloads m_IniFile, m_LoginPage and m_TemplateScript.
Note that reloading will not change the "enabled" state of the server, and it will not update listening ports. */
void Reload(void);
/** Returns the contents of the default page - the list of plugins and players */
AString GetDefaultPage(void);
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
AString GetBaseURL(const AString & a_URL);
/** Returns the list of ports used for the webadmin. */
/** Returns the list of ports on which the webadmin is configured to listen. */
AString GetPorts(void) const { return StringsConcat(m_Ports, ','); }
/** OBSOLETE: Returns the list of IPv4 ports used for the webadmin.
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
AString GetIPv4Ports(void) const { return GetPorts(); }
/** OBSOLETE: Returns the list of IPv6 ports used for the webadmin.
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
AString GetIPv6Ports(void) const { return GetPorts(); }
// tolua_end
/** Escapes text passed into it, so it can be embedded into html. */
/** Adds a new WebTab handler.
a_Title is the display title of the tab
a_UrlPath is the part of the URL that uniquely identifies this tab.
a_PluginName is the display name of the plugin creating this tab.
a_Callback is used to provide the actual WebTab contents, when requested.
Exported in ManualBindings.cpp. */
void AddWebTab(
const AString & a_Title,
const AString & a_UrlPath,
const AString & a_PluginName,
SharedPtr<cWebTabCallback> a_Callback
);
/** Removes the WebTab with the specified URL path.
Returns true if WebTab was found and removed, false if not found.
Exported in ManualBindings.cpp */
bool DelWebTab(const AString & a_UrlPath);
/** Escapes text passed into it, so it can be embedded into html.
Exported to Lua in ManualBindings.cpp. */
static AString GetHTMLEscapedString(const AString & a_Input);
/** Escapes the string for use in an URL */
/** Escapes the string for use in an URL
Exported to Lua in ManualBindings.cpp. */
static AString GetURLEncodedString(const AString & a_Input);
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style).
Exported to Lua in ManualBindings.cpp. */
static AString GetBaseURL(const AString & a_URL);
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
static AString GetBaseURL(const AStringVector & a_URLSplit);
/** Returns the content type from the file extension. If the extension isn't in the list, the function returns "text/html" */
/** Returns the content type from the file extension.
If the extension isn't in the list, the function returns an empty string.
Exported to Lua in ManualBindings.cpp. */
static AString GetContentTypeFromFileExt(const AString & a_FileExtension);
protected:
/** Protects m_WebTabs, m_TemplateScript, m_LoginTemplate and m_IniFile against multithreaded access. */
cCriticalSection m_CS;
/** All registered WebTab handlers.
Protected against multithreaded access by m_CS. */
cWebTabPtrs m_WebTabs;
/** The Lua template script to provide templates.
Protected against multithreaded access by m_CS. */
cLuaState m_TemplateScript;
/** The HTML page that provides the login.
Protected against multithreaded access by m_CS. */
AString m_LoginPage;
/** The webadmin.ini file, used for the settings and allowed logins.
Protected against multithreaded access by m_CS. */
cIniFile m_IniFile;
/** Set to true if Init() succeeds and the webadmin isn't to be disabled */
bool m_IsInitialized;
/** Set to true if Start() succeeds in starting the server, reset back to false in Stop(). */
bool m_IsRunning;
/** The webadmin.ini file, used for the settings and allowed logins */
cIniFile m_IniFile;
PluginList m_Plugins;
/** The ports on which the webadmin is running. */
AStringVector m_Ports;
/** The Lua template script to provide templates: */
cLuaState m_TemplateScript;
/** The template that provides the login site: */
AString m_LoginTemplate;
/** The HTTP server which provides the underlying HTTP parsing, serialization and events */
cHTTPServer m_HTTPServer;
/** Loads webadmin.ini into m_IniFile.
Creates a default file if it doesn't exist.
Returns true if webadmin is enabled, false if disabled. */
bool LoadIniFile(void);
/** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */
void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);