From a9b5a6c3a63d8500f3c512574fd802d40b562661 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 19 Apr 2015 10:57:41 +0200 Subject: Split the plugin names and plugin folders to avoid confusing them. Ref.: http://forum.mc-server.org/showthread.php?tid=1877 --- src/Bindings/ManualBindings.cpp | 212 +++++++++++++++------------ src/Bindings/Plugin.cpp | 34 ++++- src/Bindings/Plugin.h | 95 ++++++------ src/Bindings/PluginLua.cpp | 19 ++- src/Bindings/PluginLua.h | 5 +- src/Bindings/PluginManager.cpp | 315 +++++++++++++++++++--------------------- src/Bindings/PluginManager.h | 142 ++++++++++-------- src/Server.cpp | 19 +-- src/WebAdmin.cpp | 28 +++- 9 files changed, 482 insertions(+), 387 deletions(-) (limited to 'src') diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 6e579b364..aebdbc34b 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -513,7 +513,7 @@ static int tolua_DoWith(lua_State* tolua_S) { return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a non-empty string for parameter #1", NumArgs); } - if (!lua_isfunction( tolua_S, 3)) + if (!lua_isfunction(tolua_S, 3)) { return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #2", NumArgs); } @@ -539,20 +539,21 @@ static int tolua_DoWith(lua_State* tolua_S) class cLuaCallback : public cItemCallback { public: - cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef) - : LuaState( a_LuaState) - , FuncRef( a_FuncRef) - , TableRef( a_TableRef) - {} + cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef): + LuaState(a_LuaState), + FuncRef(a_FuncRef), + TableRef(a_TableRef) + { + } private: virtual bool Item(Ty2 * a_Item) override { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic()); if (TableRef != LUA_REFNIL) { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ } int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0); @@ -633,7 +634,8 @@ static int tolua_DoWithID(lua_State* tolua_S) LuaState(a_LuaState), FuncRef(a_FuncRef), TableRef(a_TableRef) - {} + { + } private: virtual bool Item(Ty2 * a_Item) override @@ -699,7 +701,7 @@ static int tolua_DoWithXYZ(lua_State* tolua_S) int ItemX = ((int)tolua_tonumber(tolua_S, 2, 0)); int ItemY = ((int)tolua_tonumber(tolua_S, 3, 0)); int ItemZ = ((int)tolua_tonumber(tolua_S, 4, 0)); - if (!lua_isfunction( tolua_S, 5)) + if (!lua_isfunction(tolua_S, 5)) { return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #4"); } @@ -725,20 +727,21 @@ static int tolua_DoWithXYZ(lua_State* tolua_S) class cLuaCallback : public cItemCallback { public: - cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef) - : LuaState( a_LuaState) - , FuncRef( a_FuncRef) - , TableRef( a_TableRef) - {} + cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef): + LuaState(a_LuaState), + FuncRef(a_FuncRef), + TableRef(a_TableRef) + { + } private: virtual bool Item(Ty2 * a_Item) override { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic()); if (TableRef != LUA_REFNIL) { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ } int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0); @@ -794,7 +797,7 @@ static int tolua_ForEachInChunk(lua_State * tolua_S) int ChunkX = ((int)tolua_tonumber(tolua_S, 2, 0)); int ChunkZ = ((int)tolua_tonumber(tolua_S, 3, 0)); - if (!lua_isfunction( tolua_S, 4)) + if (!lua_isfunction(tolua_S, 4)) { return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #3"); } @@ -820,20 +823,21 @@ static int tolua_ForEachInChunk(lua_State * tolua_S) class cLuaCallback : public cItemCallback { public: - cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef) - : LuaState( a_LuaState) - , FuncRef( a_FuncRef) - , TableRef( a_TableRef) - {} + cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef): + LuaState(a_LuaState), + FuncRef(a_FuncRef), + TableRef(a_TableRef) + { + } private: virtual bool Item(Ty2 * a_Item) override { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic()); if (TableRef != LUA_REFNIL) { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ } int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0); @@ -908,7 +912,8 @@ static int tolua_ForEachInBox(lua_State * tolua_S) cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FuncRef) : m_LuaState(a_LuaState), m_FnRef(a_FuncRef) - {} + { + } private: // cItemCallback overrides: @@ -960,7 +965,7 @@ static int tolua_ForEach(lua_State * tolua_S) return lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance"); } - if (!lua_isfunction( tolua_S, 2)) + if (!lua_isfunction(tolua_S, 2)) { return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #1"); } @@ -986,20 +991,21 @@ static int tolua_ForEach(lua_State * tolua_S) class cLuaCallback : public cItemCallback { public: - cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef) - : LuaState( a_LuaState) - , FuncRef( a_FuncRef) - , TableRef( a_TableRef) - {} + cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef): + LuaState(a_LuaState), + FuncRef(a_FuncRef), + TableRef(a_TableRef) + { + } private: virtual bool Item(Ty2 * a_Item) override { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ - tolua_pushusertype( LuaState, a_Item, Ty2::GetClassStatic()); + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ + tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic()); if (TableRef != LUA_REFNIL) { - lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ + lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */ } int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0); @@ -1010,7 +1016,7 @@ static int tolua_ForEach(lua_State * tolua_S) if (lua_isboolean(LuaState, -1)) { - return (tolua_toboolean( LuaState, -1, 0) > 0); + return (tolua_toboolean(LuaState, -1, 0) > 0); } return false; /* Continue enumeration */ } @@ -1447,29 +1453,12 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S) static int tolua_cPluginManager_GetAllPlugins(lua_State * tolua_S) { - cPluginManager * self = (cPluginManager *)tolua_tousertype(tolua_S, 1, nullptr); - - const cPluginManager::PluginMap & AllPlugins = self->GetAllPlugins(); + // API function no longer available: + LOGWARNING("cPluginManager:GetAllPlugins() is no longer available, use cPluginManager:ForEachPlugin() instead"); + cLuaState::LogStackTrace(tolua_S); + // Return an empty table: lua_newtable(tolua_S); - int index = 1; - cPluginManager::PluginMap::const_iterator iter = AllPlugins.begin(); - while (iter != AllPlugins.end()) - { - const cPlugin* Plugin = iter->second; - tolua_pushstring(tolua_S, iter->first.c_str()); - if (Plugin != nullptr) - { - tolua_pushusertype(tolua_S, (void *)Plugin, "const cPlugin"); - } - else - { - tolua_pushboolean(tolua_S, 0); - } - lua_rawset(tolua_S, -3); - ++iter; - ++index; - } return 1; } @@ -1493,6 +1482,18 @@ static int tolua_cPluginManager_GetCurrentPlugin(lua_State * S) +static int tolua_cPluginManager_GetPlugin(lua_State * tolua_S) +{ + // API function no longer available: + LOGWARNING("cPluginManager:GetPlugin() is no longer available. Use cPluginManager:DoWithPlugin() or cPluginManager:CallPlugin() instead."); + cLuaState::LogStackTrace(tolua_S); + return 0; +} + + + + + static int tolua_cPluginManager_LogStackTrace(lua_State * S) { cLuaState::LogStackTrace(S); @@ -1694,10 +1695,11 @@ static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S) class cLuaCallback : public cPluginManager::cCommandEnumCallback { public: - cLuaCallback(lua_State * a_LuaState, int a_FuncRef) - : LuaState( a_LuaState) - , FuncRef( a_FuncRef) - {} + cLuaCallback(lua_State * a_LuaState, int a_FuncRef): + LuaState(a_LuaState), + FuncRef(a_FuncRef) + { + } private: virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override @@ -1717,7 +1719,7 @@ static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S) if (lua_isboolean(LuaState, -1)) { - return (tolua_toboolean( LuaState, -1, 0) > 0); + return (tolua_toboolean(LuaState, -1, 0) > 0); } return false; /* Continue enumeration */ } @@ -1771,10 +1773,11 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S) class cLuaCallback : public cPluginManager::cCommandEnumCallback { public: - cLuaCallback(lua_State * a_LuaState, int a_FuncRef) - : LuaState( a_LuaState) - , FuncRef( a_FuncRef) - {} + cLuaCallback(lua_State * a_LuaState, int a_FuncRef): + LuaState(a_LuaState), + FuncRef(a_FuncRef) + { + } private: virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override @@ -1794,7 +1797,7 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S) if (lua_isboolean(LuaState, -1)) { - return (tolua_toboolean( LuaState, -1, 0) > 0); + return (tolua_toboolean(LuaState, -1, 0) > 0); } return false; /* Continue enumeration */ } @@ -2011,7 +2014,11 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S) virtual bool Item(cPlugin * a_Plugin) override { - m_NumReturns = ((cPluginLua *)a_Plugin)->CallFunctionFromForeignState( + if (!a_Plugin->IsLoaded()) + { + return false; + } + m_NumReturns = static_cast(a_Plugin)->CallFunctionFromForeignState( m_FunctionName, m_SrcLuaState, 4, lua_gettop(m_SrcLuaState) ); return true; @@ -2019,9 +2026,6 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S) } Callback(FunctionName, L); if (!cPluginManager::Get()->DoWithPlugin(PluginName, Callback)) { - // TODO 2014_01_20 _X: This might be too much logging, plugins cannot know if other plugins are loaded (async) - LOGWARNING("cPluginManager::CallPlugin: No such plugin name (\"%s\")", PluginName.c_str()); - L.LogStackTrace(); return 0; } return Callback.m_NumReturns; @@ -2031,6 +2035,21 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S) +static int tolua_cPluginManager_FindPlugins(lua_State * tolua_S) +{ + // API function no longer exists: + LOGWARNING("cPluginManager:FindPlugins() is obsolete, use cPluginManager:RefreshPluginList() instead!"); + cLuaState::LogStackTrace(tolua_S); + + // Still, do the actual work performed by the API function when it existed: + cPluginManager::Get()->RefreshPluginList(); + return 0; +} + + + + + static int tolua_cWorld_ChunkStay(lua_State * tolua_S) { /* Function signature: @@ -2337,40 +2356,40 @@ static int tolua_cPluginLua_AddWebTab(lua_State * tolua_S) -static int tolua_cPluginLua_AddTab(lua_State* tolua_S) +static int tolua_cPlugin_GetDirectory(lua_State * tolua_S) { - cPluginLua * self = (cPluginLua *) tolua_tousertype(tolua_S, 1, nullptr); - LOGWARN("WARNING: Using deprecated function AddTab()! Use AddWebTab() instead. (plugin \"%s\" in folder \"%s\")", - self->GetName().c_str(), self->GetDirectory().c_str() - ); - return tolua_cPluginLua_AddWebTab( tolua_S); + cLuaState L(tolua_S); + + // Log the obsoletion warning: + LOGWARNING("cPlugin:GetDirectory() is obsolete, use cPlugin:GetFolderName() instead."); + L.LogStackTrace(); + + // Retrieve the params: + cPlugin * Plugin = static_cast(tolua_tousertype(tolua_S, 1, nullptr)); + + // Get the folder name: + L.Push(Plugin->GetFolderName()); + return 1; } -static int tolua_cPlugin_Call(lua_State * tolua_S) +static int tolua_cPlugin_GetLocalDirectory(lua_State * tolua_S) { cLuaState L(tolua_S); // Log the obsoletion warning: - LOGWARNING("cPlugin:Call() is obsolete and unsafe, use cPluginManager:CallPlugin() instead."); + LOGWARNING("cPlugin:GetLocalDirectory() is obsolete, use cPlugin:GetLocalFolder() instead."); L.LogStackTrace(); - // Retrieve the params: plugin and the function name to call - cPluginLua * TargetPlugin = (cPluginLua *) tolua_tousertype(tolua_S, 1, nullptr); - AString FunctionName = tolua_tostring(tolua_S, 2, ""); + // Retrieve the params: + cPlugin * Plugin = static_cast(tolua_tousertype(tolua_S, 1, nullptr)); - // Call the function: - int NumReturns = TargetPlugin->CallFunctionFromForeignState(FunctionName, L, 3, lua_gettop(L)); - if (NumReturns < 0) - { - LOGWARNING("cPlugin::Call() failed to call destination function"); - L.LogStackTrace(); - return 0; - } - return NumReturns; + // Get the folder: + L.Push(Plugin->GetLocalFolder()); + return 1; } @@ -2639,8 +2658,8 @@ static int tolua_cWebPlugin_GetTabNames(lua_State * tolua_S) { const AString & FancyName = iter->first; const AString & WebName = iter->second; - tolua_pushstring( tolua_S, WebName.c_str()); // Because the WebName is supposed to be unique, use it as key - tolua_pushstring( tolua_S, FancyName.c_str()); + tolua_pushstring(tolua_S, WebName.c_str()); // Because the WebName is supposed to be unique, use it as key + tolua_pushstring(tolua_S, FancyName.c_str()); // lua_rawset(tolua_S, -3); ++iter; @@ -3792,7 +3811,8 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cPlugin"); - tolua_function(tolua_S, "Call", tolua_cPlugin_Call); + tolua_function(tolua_S, "GetDirectory", tolua_cPlugin_GetDirectory); + tolua_function(tolua_S, "GetLocalDirectory", tolua_cPlugin_GetLocalDirectory); tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cPluginManager"); @@ -3800,10 +3820,13 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "BindCommand", tolua_cPluginManager_BindCommand); tolua_function(tolua_S, "BindConsoleCommand", tolua_cPluginManager_BindConsoleCommand); tolua_function(tolua_S, "CallPlugin", tolua_cPluginManager_CallPlugin); + tolua_function(tolua_S, "FindPlugins", tolua_cPluginManager_FindPlugins); tolua_function(tolua_S, "ForEachCommand", tolua_cPluginManager_ForEachCommand); tolua_function(tolua_S, "ForEachConsoleCommand", tolua_cPluginManager_ForEachConsoleCommand); + tolua_function(tolua_S, "ForEachPlugin", tolua_ForEach); tolua_function(tolua_S, "GetAllPlugins", tolua_cPluginManager_GetAllPlugins); tolua_function(tolua_S, "GetCurrentPlugin", tolua_cPluginManager_GetCurrentPlugin); + tolua_function(tolua_S, "GetPlugin", tolua_cPluginManager_GetPlugin); tolua_function(tolua_S, "LogStackTrace", tolua_cPluginManager_LogStackTrace); tolua_endmodule(tolua_S); @@ -3819,7 +3842,6 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cPluginLua"); - tolua_function(tolua_S, "AddTab", tolua_cPluginLua_AddTab); tolua_function(tolua_S, "AddWebTab", tolua_cPluginLua_AddWebTab); tolua_endmodule(tolua_S); diff --git a/src/Bindings/Plugin.cpp b/src/Bindings/Plugin.cpp index 98ccfb88c..2f2771e38 100644 --- a/src/Bindings/Plugin.cpp +++ b/src/Bindings/Plugin.cpp @@ -7,11 +7,11 @@ -cPlugin::cPlugin(const AString & a_PluginDirectory) : - m_Language(E_CPP), - m_Name(a_PluginDirectory), +cPlugin::cPlugin(const AString & a_FolderName) : + m_Status(cPluginManager::psDisabled), + m_Name(a_FolderName), m_Version(0), - m_Directory(a_PluginDirectory) + m_FolderName(a_FolderName) { } @@ -28,9 +28,33 @@ cPlugin::~cPlugin() +void cPlugin::Unload(void) +{ + auto pm = cPluginManager::Get(); + pm->RemovePluginCommands(this); + pm->RemovePluginConsoleCommands(this); + pm->RemoveHooks(this); + OnDisable(); + m_Status = cPluginManager::psUnloaded; + m_LoadError.clear(); +} + + + + + AString cPlugin::GetLocalFolder(void) const { - return std::string("Plugins/") + m_Directory; + return std::string("Plugins/") + m_FolderName; +} + + + + +void cPlugin::SetLoadError(const AString & a_LoadError) +{ + m_Status = cPluginManager::psError; + m_LoadError = a_LoadError; } diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 3f9fa7655..5c43f9042 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -1,30 +1,16 @@ -#pragma once - -#include "Defines.h" +// Plugin.h -class cCommandOutputCallback; -class cItems; -class cHopperEntity; +// Declares the cPlugin class representing an interface that a plugin implementation needs to expose, with some helping functions -class cBlockEntityWithItems; -class cClientHandle; -class cPickup; -class cPlayer; -class cProjectileEntity; -class cEntity; -class cMonster; -class cWorld; -class cChunkDesc; -struct TakeDamageInfo; -// fwd: CraftingRecipes.h -class cCraftingGrid; -class cCraftingRecipe; +#pragma once +#include "Defines.h" +#include "PluginManager.h" @@ -35,11 +21,23 @@ class cPlugin public: // tolua_end - cPlugin( const AString & a_PluginDirectory); + /** Creates a new instance. + a_FolderName is the name of the folder (in the Plugins folder) from which the plugin is loaded. + The plugin's name defaults to the folder name. */ + cPlugin(const AString & a_FolderName); + virtual ~cPlugin(); + /** Called as the last call into the plugin before it is unloaded. */ virtual void OnDisable(void) {} - virtual bool Initialize(void) = 0; + + /** Loads and initializes the plugin. Sets m_Status to psLoaded or psError accordingly. + Returns true if the initialization succeeded, false otherwise. */ + virtual bool Load(void) = 0; + + /** Unloads the plugin. Sets m_Status to psDisabled. + The default implementation removes the plugin's associations with cPluginManager, descendants should call it as well. */ + virtual void Unload(void); // Called each tick virtual void Tick(float a_Dt) = 0; @@ -109,19 +107,17 @@ public: /** Handles the command split into a_Split, issued by player a_Player. Command permissions have already been checked. - Returns true if command handled successfully - */ + 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. - */ + 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 + /** 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 + /** All bound console commands are to be removed, do any language-dependent cleanup here */ virtual void ClearConsoleCommands(void) {} // tolua_begin @@ -131,28 +127,43 @@ public: int GetVersion(void) const { return m_Version; } void SetVersion(int a_Version) { m_Version = a_Version; } - const AString & GetDirectory(void) const {return m_Directory; } - AString GetLocalDirectory(void) const {return GetLocalFolder(); } // OBSOLETE, use GetLocalFolder() instead + /** Returns the name of the folder (in the Plugins folder) from which the plugin is loaded. */ + const AString & GetFolderName(void) const {return m_FolderName; } + + /** Returns the folder relative to the MCS Executable, from which the plugin is loaded. */ AString GetLocalFolder(void) const; + + /** Returns the error encountered while loading the plugin. Only valid if m_Status == psError. */ + const AString & GetLoadError(void) const { return m_LoadError; } + + cPluginManager::ePluginStatus GetStatus(void) const { return m_Status; } + + bool IsLoaded(void) const { return (m_Status == cPluginManager::psLoaded); } // tolua_end + // Needed for ManualBindings' tolua_ForEach<> + static const char * GetClassStatic(void) { return "cPlugin"; } + +protected: + friend class cPluginManager; + + cPluginManager::ePluginStatus m_Status; - /* This should not be exposed to scripting languages */ - enum PluginLanguage - { - E_CPP, - E_LUA, - E_SQUIRREL, // OBSOLETE, but kept in place to remind us of the horrors lurking in the history - }; - PluginLanguage GetLanguage() { return m_Language; } - void SetLanguage( PluginLanguage a_Language) { m_Language = a_Language; } - -private: - PluginLanguage m_Language; + /** The name of the plugin, used to identify the plugin in the system and for inter-plugin calls. */ AString m_Name; + int m_Version; - AString m_Directory; + /** Name of the folder (in the Plugins folder) from which the plugin is loaded. */ + AString m_FolderName; + + /** The error encountered while loading the plugin. + Only valid if m_Status == psError. */ + AString m_LoadError; + + + /** Sets m_LoadError to the specified string and m_Status to psError. */ + void SetLoadError(const AString & a_LoadError); }; // tolua_export diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 9bbac92b0..cb2ea3b45 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -93,7 +93,7 @@ void cPluginLua::Close(void) -bool cPluginLua::Initialize(void) +bool cPluginLua::Load(void) { cCSLock Lock(m_CriticalSection); if (!m_LuaState.IsValid()) @@ -144,6 +144,7 @@ bool cPluginLua::Initialize(void) // Warn if there are no Lua files in the plugin folder: if (LuaFiles.empty()) { + SetLoadError("No lua files found, plugin is probably missing."); LOGWARNING("No lua files found: plugin %s is missing.", GetName().c_str()); Close(); return false; @@ -155,6 +156,7 @@ bool cPluginLua::Initialize(void) AString Path = PluginPath + *itr; if (!m_LuaState.LoadFile(Path)) { + SetLoadError(Printf("Failed to load file %s.", itr->c_str())); Close(); return false; } @@ -164,6 +166,8 @@ bool cPluginLua::Initialize(void) AString Path = PluginPath + "Info.lua"; if (!m_LuaState.LoadFile(Path)) { + SetLoadError("Failed to load file Info.lua."); + m_Status = cPluginManager::psError; Close(); return false; } @@ -173,17 +177,20 @@ bool cPluginLua::Initialize(void) bool res = false; if (!m_LuaState.Call("Initialize", this, cLuaState::Return, res)) { + SetLoadError("Cannot call the Initialize() function."); LOGWARNING("Error in plugin %s: Cannot call the Initialize() function. Plugin is temporarily disabled.", GetName().c_str()); Close(); return false; } if (!res) { + SetLoadError("The Initialize() function failed."); LOGINFO("Plugin %s: Initialize() call failed, plugin is temporarily disabled.", GetName().c_str()); Close(); return false; } + m_Status = cPluginManager::psLoaded; return true; } @@ -191,6 +198,16 @@ bool cPluginLua::Initialize(void) +void cPluginLua::Unload(void) +{ + super::Unload(); + Close(); +} + + + + + void cPluginLua::OnDisable(void) { cCSLock Lock(m_CriticalSection); diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index c14b02687..70d203244 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -32,6 +32,8 @@ class cPluginLua : public cPlugin, public cWebPlugin { + typedef cPlugin super; + public: // tolua_end @@ -96,7 +98,8 @@ public: ~cPluginLua(); virtual void OnDisable(void) override; - virtual bool Initialize(void) override; + virtual bool Load(void) override; + virtual void Unload(void) override; virtual void Tick(float a_Dt) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 8935f7dd3..45a778eda 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -59,39 +59,48 @@ void cPluginManager::ReloadPlugins(void) -void cPluginManager::FindPlugins(void) +void cPluginManager::RefreshPluginList(void) { + // Get a list of currently available folders: AString PluginsPath = GetPluginsPath() + "/"; - - // First get a clean list of only the currently running plugins, we don't want to mess those up - for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end();) + AStringVector Contents = cFile::GetFolderContents(PluginsPath.c_str()); + AStringVector Folders; + for (auto & item: Contents) { - if (itr->second == nullptr) + if ((item == ".") || (item == "..") || (!cFile::IsFolder(PluginsPath + item))) { - PluginMap::iterator thiz = itr; - ++thiz; - m_Plugins.erase( itr); - itr = thiz; + // We only want folders, and don't want "." or ".." continue; } - ++itr; - } + Folders.push_back(item); + } // for item - Contents[] - AStringVector Files = cFile::GetFolderContents(PluginsPath.c_str()); - for (AStringVector::const_iterator itr = Files.begin(); itr != Files.end(); ++itr) + // Set all plugins with invalid folders as psNotFound: + for (auto & plugin: m_Plugins) { - if ((*itr == ".") || (*itr == "..") || (!cFile::IsFolder(PluginsPath + *itr))) + if (std::find(Folders.cbegin(), Folders.cend(), plugin->GetFolderName()) == Folders.end()) { - // We only want folders, and don't want "." or ".." - continue; + plugin->m_Status = psNotFound; } + } // for plugin - m_Plugins[] - // Add plugin name/directory to the list - if (m_Plugins.find(*itr) == m_Plugins.end()) + // Add all newly discovered plugins: + for (auto & folder: Folders) + { + bool hasFound = false; + for (auto & plugin: m_Plugins) { - m_Plugins[*itr] = nullptr; + if (plugin->GetFolderName() == folder) + { + hasFound = true; + break; + } + } // for plugin - m_Plugins[] + if (!hasFound) + { + m_Plugins.push_back(std::make_shared(folder)); } - } + } // for folder - Folders[] } @@ -112,57 +121,23 @@ void cPluginManager::ReloadPluginsNow(void) void cPluginManager::ReloadPluginsNow(cIniFile & a_SettingsIni) { LOG("-- Loading Plugins --"); + + // Unload any existing plugins: m_bReloadPlugins = false; UnloadPluginsNow(); - FindPlugins(); - - cServer::BindBuiltInConsoleCommands(); - - // Check if the Plugins section exists. - int KeyNum = a_SettingsIni.FindKey("Plugins"); + // Refresh the list of plugins to load new ones from disk / remove the deleted ones: + RefreshPluginList(); - if (KeyNum == -1) + // Load the plugins: + AStringVector ToLoad = GetFoldersToLoad(a_SettingsIni); + for (auto & pluginFolder: ToLoad) { - InsertDefaultPlugins(a_SettingsIni); - KeyNum = a_SettingsIni.FindKey("Plugins"); - } - - // How many plugins are there? - int NumPlugins = a_SettingsIni.GetNumValues(KeyNum); - - for (int i = 0; i < NumPlugins; i++) - { - AString ValueName = a_SettingsIni.GetValueName(KeyNum, i); - if (ValueName.compare("Plugin") == 0) - { - AString PluginFile = a_SettingsIni.GetValue(KeyNum, i); - if (!PluginFile.empty()) - { - if (m_Plugins.find(PluginFile) != m_Plugins.end()) - { - LoadPlugin(PluginFile); - } - } - } - } - - - // Remove invalid plugins from the PluginMap. - for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end();) - { - if (itr->second == nullptr) - { - PluginMap::iterator thiz = itr; - ++thiz; - m_Plugins.erase(itr); - itr = thiz; - continue; - } - ++itr; - } + LoadPlugin(pluginFolder); + } // for pluginFolder - ToLoad[] - size_t NumLoadedPlugins = GetNumPlugins(); + // Log a report of the loading process + size_t NumLoadedPlugins = GetNumLoadedPlugins(); if (NumLoadedPlugins == 0) { LOG("-- No Plugins Loaded --"); @@ -173,7 +148,7 @@ void cPluginManager::ReloadPluginsNow(cIniFile & a_SettingsIni) } else { - LOG("-- Loaded %i Plugins --", (int)NumLoadedPlugins); + LOG("-- Loaded %u Plugins --", static_cast(NumLoadedPlugins)); } CallHookPluginsLoaded(); } @@ -200,12 +175,21 @@ void cPluginManager::InsertDefaultPlugins(cIniFile & a_SettingsIni) void cPluginManager::Tick(float a_Dt) { - while (!m_DisablePluginList.empty()) + // Unload plugins that have been scheduled for unloading: + AStringVector PluginsToUnload; { - RemovePlugin(m_DisablePluginList.front()); - m_DisablePluginList.pop_front(); + cCSLock Lock(m_CSPluginsToUnload); + std::swap(m_PluginsToUnload, PluginsToUnload); } + for (auto & plugin: m_Plugins) + { + if (std::find(PluginsToUnload.cbegin(), PluginsToUnload.cend(), plugin->GetFolderName()) != PluginsToUnload.cend()) + { + plugin->Unload(); + } + } // for plugin - m_Plugins[] + // If a plugin reload has been scheduled, reload now: if (m_bReloadPlugins) { ReloadPluginsNow(); @@ -1477,68 +1461,56 @@ cPluginManager::CommandResult cPluginManager::HandleCommand(cPlayer & a_Player, -cPlugin * cPluginManager::GetPlugin(const AString & a_Plugin) const +void cPluginManager::UnloadPluginsNow() { - for (PluginMap::const_iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr) - { - if (itr->second == nullptr) - { - // The plugin is currently unloaded - continue; - } + // Remove all bindings: + m_Hooks.clear(); + m_Commands.clear(); + m_ConsoleCommands.clear(); + + // Re-bind built-in console commands: + cServer::BindBuiltInConsoleCommands(); - if (itr->second->GetName().compare(a_Plugin) == 0) + // Unload all loaded plugins: + for (auto & plugin: m_Plugins) + { + if (plugin->IsLoaded()) { - return itr->second; + plugin->Unload(); } } - return 0; } -const cPluginManager::PluginMap & cPluginManager::GetAllPlugins() const +void cPluginManager::UnloadPlugin(const AString & a_PluginFolder) { - return m_Plugins; + cCSLock Lock(m_CSPluginsToUnload); + m_PluginsToUnload.push_back(a_PluginFolder); } -void cPluginManager::UnloadPluginsNow() +bool cPluginManager::LoadPlugin(const AString & a_FolderName) { - m_Hooks.clear(); - - while (!m_Plugins.empty()) + for (auto & plugin: m_Plugins) { - RemovePlugin(m_Plugins.begin()->second); - } - - m_Commands.clear(); - m_ConsoleCommands.clear(); -} - - - - - -bool cPluginManager::DisablePlugin(const AString & a_PluginName) -{ - PluginMap::iterator itr = m_Plugins.find(a_PluginName); - if (itr == m_Plugins.end()) - { - return false; - } + if (plugin->GetFolderName() == a_FolderName) + { + if (!plugin->IsLoaded()) + { + return plugin->Load(); + } + return true; + } + } // for plugin - m_Plugins[] - if (itr->first.compare(a_PluginName) == 0) // _X 2013_02_01: wtf? Isn't this supposed to be what find() does? - { - m_DisablePluginList.push_back(itr->second); - itr->second = nullptr; // Get rid of this thing right away - return true; - } + // Plugin not found + LOGD("%s: Plugin folder %s not found in the list of plugins.", __FUNCTION__, a_FolderName.c_str()); return false; } @@ -1546,15 +1518,6 @@ bool cPluginManager::DisablePlugin(const AString & a_PluginName) -bool cPluginManager::LoadPlugin(const AString & a_PluginName) -{ - return AddPlugin(new cPluginLua(a_PluginName.c_str())); -} - - - - - void cPluginManager::RemoveHooks(cPlugin * a_Plugin) { for (HookMap::iterator itr = m_Hooks.begin(), end = m_Hooks.end(); itr != end; ++itr) @@ -1567,32 +1530,6 @@ void cPluginManager::RemoveHooks(cPlugin * a_Plugin) -void cPluginManager::RemovePlugin(cPlugin * a_Plugin) -{ - for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr) - { - if (itr->second == a_Plugin) - { - m_Plugins.erase(itr); - break; - } - } - - RemovePluginCommands(a_Plugin); - RemovePluginConsoleCommands(a_Plugin); - RemoveHooks(a_Plugin); - if (a_Plugin != nullptr) - { - a_Plugin->OnDisable(); - } - delete a_Plugin; - a_Plugin = nullptr; -} - - - - - void cPluginManager::RemovePluginCommands(cPlugin * a_Plugin) { if (a_Plugin != nullptr) @@ -1836,31 +1773,31 @@ bool cPluginManager::IsValidHookType(int a_HookType) bool cPluginManager::DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback) { // TODO: Implement locking for plugins - PluginMap::iterator itr = m_Plugins.find(a_PluginName); - if ((itr == m_Plugins.end()) || (itr->second == nullptr)) + for (auto & plugin: m_Plugins) { - return false; + if (plugin->GetName() == a_PluginName) + { + return a_Callback.Item(plugin.get()); + } } - return a_Callback.Item(itr->second); + return false; } -bool cPluginManager::AddPlugin(cPlugin * a_Plugin) +bool cPluginManager::ForEachPlugin(cPluginCallback & a_Callback) { - m_Plugins[a_Plugin->GetDirectory()] = a_Plugin; - - if (a_Plugin->Initialize()) + // TODO: Implement locking for plugins + for (auto & plugin: m_Plugins) { - // Initialization OK - return true; + if (a_Callback.Item(plugin.get())) + { + return false; + } } - - // Initialization failed - RemovePlugin(a_Plugin); // Also undoes any registrations that Initialize() might have made - return false; + return true; } @@ -1869,21 +1806,23 @@ bool cPluginManager::AddPlugin(cPlugin * a_Plugin) void cPluginManager::AddHook(cPlugin * a_Plugin, int a_Hook) { - if (!a_Plugin) + if (a_Plugin == nullptr) { LOGWARN("Called cPluginManager::AddHook() with a_Plugin == nullptr"); return; } PluginList & Plugins = m_Hooks[a_Hook]; - Plugins.remove(a_Plugin); - Plugins.push_back(a_Plugin); + if (std::find(Plugins.cbegin(), Plugins.cend(), a_Plugin) == Plugins.cend()) + { + Plugins.push_back(a_Plugin); + } } -size_t cPluginManager::GetNumPlugins() const +size_t cPluginManager::GetNumPlugins(void) const { return m_Plugins.size(); } @@ -1891,3 +1830,53 @@ size_t cPluginManager::GetNumPlugins() const + +size_t cPluginManager::GetNumLoadedPlugins(void) const +{ + size_t res = 0; + for (auto & plugin: m_Plugins) + { + if (plugin->IsLoaded()) + { + res += 1; + } + } + return res; +} + + + + + +AStringVector cPluginManager::GetFoldersToLoad(cIniFile & a_SettingsIni) +{ + // Check if the Plugins section exists. + int KeyNum = a_SettingsIni.FindKey("Plugins"); + if (KeyNum == -1) + { + InsertDefaultPlugins(a_SettingsIni); + KeyNum = a_SettingsIni.FindKey("Plugins"); + } + + // Get the list of plugins to load: + AStringVector res; + int NumPlugins = a_SettingsIni.GetNumValues(KeyNum); + for (int i = 0; i < NumPlugins; i++) + { + AString ValueName = a_SettingsIni.GetValueName(KeyNum, i); + if (ValueName.compare("Plugin") == 0) + { + AString PluginFile = a_SettingsIni.GetValue(KeyNum, i); + if (!PluginFile.empty()) + { + res.push_back(PluginFile); + } + } + } // for i - ini values + + return res; +} + + + + diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index be6ff021a..f32feb8a3 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -6,48 +6,30 @@ -class cPlugin; -// fwd: World.h -class cWorld; -// fwd: ChunkDesc.h +// fwd: +class cBlockEntityWithItems; class cChunkDesc; - -// fwd: Entities/Entity.h -class cEntity; - -// fwd: Entities/ProjectileEntity.h -class cProjectileEntity; - -// fwd: Mobs/Monster.h -class cMonster; - -// fwd: Player.h -class cPlayer; - -// fwd: CraftingRecipes.h +class cClientHandle; +class cCommandOutputCallback; class cCraftingGrid; class cCraftingRecipe; - -// fwd: Pickup.h +class cEntity; +class cHopperEntity; +class cItems; +class cMonster; class cPickup; - -// fwd: Pawn.h +class cPlayer; +class cPlugin; +class cProjectileEntity; +class cWorld; struct TakeDamageInfo; -// fwd: CommandOutput.h -class cCommandOutputCallback; +typedef SharedPtr cPluginPtr; +typedef std::vector cPluginPtrs; -// fwd: BlockEntities/HopperEntity.h -class cHopperEntity; -// fwd: BlockEntities/BlockEntityWithItems.h -class cBlockEntityWithItems; - - - -class cItems; @@ -55,12 +37,7 @@ class cItems; class cPluginManager { public: - // tolua_end - - // Called each tick - virtual void Tick(float a_Dt); - - // tolua_begin + enum CommandResult { crExecuted, @@ -70,6 +47,29 @@ public: crNoPermission, } ; + + /** Defines the status of a single plugin - whether it is loaded, disabled or errored. */ + enum ePluginStatus + { + /** The plugin has been loaded successfully. */ + psLoaded, + + /** The plugin is disabled in settings.ini. */ + psDisabled, + + /** The plugin is enabled in settings.ini but has been unloaded (by a command). */ + psUnloaded, + + /** The plugin is enabled in settings.ini but has failed to load. + m_LoadError is the description of the error. */ + psError, + + /** The plugin has been loaded before, but after a folder refresh it is no longer present. + The plugin will be unloaded in the next call to ReloadPlugins(). */ + psNotFound, + }; + + enum PluginHook { HOOK_BLOCK_SPREAD, @@ -160,24 +160,31 @@ public: /** The interface used for enumerating and extern-calling plugins */ typedef cItemCallback cPluginCallback; + typedef std::list PluginList; + + + /** Called each tick, calls the plugins' OnTick hook, as well as processes plugin events (addition, removal) */ + void Tick(float a_Dt); /** Returns the instance of the Plugin Manager (there is only ever one) */ static cPluginManager * Get(void); // tolua_export - typedef std::map< AString, cPlugin * > PluginMap; - typedef std::list< cPlugin * > PluginList; - cPlugin * GetPlugin( const AString & a_Plugin) const; // tolua_export - const PluginMap & GetAllPlugins() const; // >> EXPORTED IN MANUALBINDINGS << + /** Refreshes the m_Plugins list based on the current contents of the Plugins folder. + If an active plugin's folder is not found anymore, the plugin is set as psNotFound, but not yet unloaded. */ + void RefreshPluginList(); // tolua_export - // tolua_begin - void FindPlugins(); - void ReloadPlugins(); - // tolua_end + /** Schedules a reload of the plugins to happen within the next call to Tick(). */ + void ReloadPlugins(); // tolua_export - /** Adds the plugin to the list of plugins called for the specified hook type. Handles multiple adds as a single add */ + /** Adds the plugin to the list of plugins called for the specified hook type. + If a plugin adds multiple handlers for a single hook, it is added only once (ignore-duplicates). */ void AddHook(cPlugin * a_Plugin, int a_HookType); + /** Returns the number of all plugins in m_Plugins (includes disabled, unloaded and errored plugins). */ size_t GetNumPlugins() const; // tolua_export + + /** Returns the number of plugins that are psLoaded. */ + size_t GetNumLoadedPlugins(void) const; // tolua_export // Calls for individual hooks. Each returns false if the action is to continue or true if the plugin wants to abort bool CallHookBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source); @@ -242,14 +249,19 @@ public: bool CallHookWorldStarted (cWorld & a_World); bool CallHookWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec); - bool DisablePlugin(const AString & a_PluginName); // tolua_export - bool LoadPlugin (const AString & a_PluginName); // tolua_export + /** Queues the specified plugin to be unloaded in the next call to Tick(). + Note that this function returns before the plugin is unloaded, to avoid deadlocks. */ + void UnloadPlugin(const AString & a_PluginFolder); // tolua_export + + /** Loads the plugin from the specified plugin folder. + Returns true if the plugin was loaded successfully or was already loaded before, false otherwise. */ + bool LoadPlugin(const AString & a_PluginFolder); // tolua_export /** Removes all hooks the specified plugin has registered */ void RemoveHooks(cPlugin * a_Plugin); - /** Removes the plugin from the internal structures and deletes its object. */ - void RemovePlugin(cPlugin * a_Plugin); + /** Removes the plugin of the specified name from the internal structures and deletes its object. */ + void RemovePlugin(const AString & a_PluginName); /** Removes all command bindings that the specified plugin has made */ void RemovePluginCommands(cPlugin * a_Plugin); @@ -296,8 +308,12 @@ public: static bool IsValidHookType(int a_HookType); /** Calls the specified callback with the plugin object of the specified plugin. - Returns false if plugin not found, and the value that the callback has returned otherwise. */ + Returns false if plugin not found, otherwise returns the value that the callback has returned. */ bool DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback); + + /** Calls the specified callback for each plugin in m_Plugins. + 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 path where individual plugins' folders are expected. The path doesn't end in a slash. */ @@ -317,14 +333,26 @@ private: typedef std::map HookMap; typedef std::map CommandMap; - PluginList m_DisablePluginList; - PluginMap m_Plugins; + + /** FolderNames of plugins that should be unloaded. + The plugins will be unloaded within the next call to Tick(), to avoid multithreading issues. + Protected against multithreaded access by m_CSPluginsToUnload. */ + AStringVector m_PluginsToUnload; + + /** Protects m_PluginsToUnload against multithreaded access. */ + mutable cCriticalSection m_CSPluginsToUnload; + + /** All plugins that have been found in the Plugins folder. */ + cPluginPtrs m_Plugins; + HookMap m_Hooks; CommandMap m_Commands; CommandMap m_ConsoleCommands; + /** If set to true, all the plugins will be reloaded within the next call to Tick(). */ bool m_bReloadPlugins; + cPluginManager(); virtual ~cPluginManager(); @@ -340,11 +368,11 @@ private: /** Handles writing default plugins if 'Plugins' key not found using a cIniFile object expected to be intialised to settings.ini */ void InsertDefaultPlugins(cIniFile & a_SettingsIni); - /** Adds the plugin into the internal list of plugins and initializes it. If initialization fails, the plugin is removed again. */ - bool AddPlugin(cPlugin * a_Plugin); - /** Tries to match a_Command to the internal table of commands, if a match is found, the corresponding plugin is called. Returns crExecuted if the command is executed. */ CommandResult HandleCommand(cPlayer & a_Player, const AString & a_Command, bool a_ShouldCheckPermissions); + + /** Returns the folders that are specified in the settings ini to load plugins from. */ + AStringVector GetFoldersToLoad(cIniFile & a_SettingsIni); } ; // tolua_export diff --git a/src/Server.cpp b/src/Server.cpp index df2c7deef..9c5c6b6eb 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -464,22 +464,11 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac { if (split.size() > 1) { - cPluginManager::PluginMap map = cPluginManager::Get()->GetAllPlugins(); - - for (auto plugin_entry : map) - { - if (plugin_entry.first == split[1]) - { - a_Output.Out("Error! Plugin is already loaded!"); - a_Output.Finished(); - return; - } - } a_Output.Out(cPluginManager::Get()->LoadPlugin(split[1]) ? "Plugin loaded" : "Error occurred loading plugin"); } else { - a_Output.Out("Usage: load "); + a_Output.Out("Usage: load "); } a_Output.Finished(); return; @@ -488,12 +477,12 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac { if (split.size() > 1) { - cPluginManager::Get()->RemovePlugin(cPluginManager::Get()->GetPlugin(split[1])); - a_Output.Out("Plugin unloaded"); + cPluginManager::Get()->UnloadPlugin(split[1]); + a_Output.Out("Plugin unload scheduled"); } else { - a_Output.Out("Usage: unload "); + a_Output.Out("Usage: unload "); } a_Output.Finished(); return; diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 13cf3cc41..ec265ea5b 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -489,20 +489,32 @@ AString cWebAdmin::GetDefaultPage(void) Content += "

Server Name:

"; Content += "

" + AString( cRoot::Get()->GetServer()->GetServerID()) + "

"; + // Display a list of all plugins: Content += "

Plugins:

    "; - cPluginManager * PM = cPluginManager::Get(); - const cPluginManager::PluginMap & List = PM->GetAllPlugins(); - for (cPluginManager::PluginMap::const_iterator itr = List.begin(); itr != List.end(); ++itr) + struct cPluginCallback: + public cPluginManager::cPluginCallback { - if (itr->second == nullptr) + AString & m_Content; + + cPluginCallback(AString & a_Content): + m_Content(a_Content) { - continue; } - AppendPrintf(Content, "
  • %s V.%i
  • ", itr->second->GetName().c_str(), itr->second->GetVersion()); - } + + virtual bool Item(cPlugin * a_Plugin) override + { + if (a_Plugin->IsLoaded()) + { + AppendPrintf(m_Content, "
  • %s V.%i
  • ", a_Plugin->GetName().c_str(), a_Plugin->GetVersion()); + } + return false; + } + } Callback(Content); + cPluginManager::Get()->ForEachPlugin(Callback); Content += "
"; - Content += "

Players:

    "; + // Display a list of all players: + Content += "

    Players:

      "; cPlayerAccum PlayerAccum; cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players if (World != nullptr) -- cgit v1.2.3 From 288d2280fa85225613534ae56a59e35465bbefe2 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 19 Apr 2015 14:35:04 +0200 Subject: Refactored cWebPlugin for C++11 style and proper WebTab clearing. --- src/Bindings/ManualBindings.cpp | 20 +++----- src/Bindings/PluginLua.cpp | 48 ++++++------------ src/Bindings/PluginLua.h | 9 ++-- src/Bindings/WebPlugin.cpp | 110 ++++++++++++++++++++++++++-------------- src/Bindings/WebPlugin.h | 61 +++++++++++++++++----- src/WebAdmin.cpp | 4 +- 6 files changed, 149 insertions(+), 103 deletions(-) (limited to 'src') diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index aebdbc34b..4c8d9ff96 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -2647,22 +2647,16 @@ static int tolua_AllToLua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S) static int tolua_cWebPlugin_GetTabNames(lua_State * tolua_S) { - cWebPlugin* self = (cWebPlugin*) tolua_tousertype(tolua_S, 1, nullptr); - - const cWebPlugin::TabNameList & TabNames = self->GetTabNames(); - + // Returns a map of (SafeTitle -> Title) for the plugin's web tabs. + auto self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); + auto TabNames = self->GetTabNames(); lua_newtable(tolua_S); int index = 1; - cWebPlugin::TabNameList::const_iterator iter = TabNames.begin(); - while (iter != TabNames.end()) - { - const AString & FancyName = iter->first; - const AString & WebName = iter->second; - tolua_pushstring(tolua_S, WebName.c_str()); // Because the WebName is supposed to be unique, use it as key - tolua_pushstring(tolua_S, FancyName.c_str()); - // + 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); - ++iter; ++index; } return 1; diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index cb2ea3b45..ddd3398a5 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -200,6 +200,7 @@ bool cPluginLua::Load(void) void cPluginLua::Unload(void) { + ClearTabs(); super::Unload(); Close(); } @@ -2000,40 +2001,29 @@ void cPluginLua::AddResettable(cPluginLua::cResettablePtr a_Resettable) -AString cPluginLua::HandleWebRequest(const HTTPRequest * a_Request) +AString cPluginLua::HandleWebRequest(const HTTPRequest & a_Request) { - cCSLock Lock(m_CriticalSection); - std::string RetVal = ""; - - std::pair< std::string, std::string > TabName = GetTabNameForRequest(a_Request); - std::string SafeTabName = TabName.second; - if (SafeTabName.empty()) + // Find the tab to use for the request: + auto TabName = GetTabNameForRequest(a_Request); + AString SafeTabTitle = TabName.second; + if (SafeTabTitle.empty()) { return ""; } - - sWebPluginTab * Tab = 0; - for (TabList::iterator itr = GetTabs().begin(); itr != GetTabs().end(); ++itr) + auto Tab = GetTabBySafeTitle(SafeTabTitle); + if (Tab == nullptr) { - if ((*itr)->SafeTitle.compare(SafeTabName) == 0) // This is the one! Rawr - { - Tab = *itr; - break; - } + return ""; } - if (Tab != nullptr) + // Get the page content from the plugin: + cCSLock Lock(m_CriticalSection); + AString Contents = Printf("WARNING: WebPlugin tab '%s' did not return a string!", Tab->m_Title.c_str()); + if (!m_LuaState.Call(Tab->m_UserData, &a_Request, cLuaState::Return, Contents)) { - AString Contents = Printf("WARNING: WebPlugin tab '%s' did not return a string!", Tab->Title.c_str()); - if (!m_LuaState.Call(Tab->UserData, a_Request, cLuaState::Return, Contents)) - { - return "Lua encountered error while processing the page request"; - } - - RetVal += Contents; + return "Lua encountered error while processing the page request"; } - - return RetVal; + return Contents; } @@ -2048,13 +2038,7 @@ bool cPluginLua::AddWebTab(const AString & a_Title, lua_State * a_LuaState, int LOGERROR("Only allowed to add a tab to a WebPlugin of your own Plugin!"); return false; } - sWebPluginTab * Tab = new sWebPluginTab(); - Tab->Title = a_Title; - Tab->SafeTitle = SafeString(a_Title); - - Tab->UserData = a_FunctionReference; - - GetTabs().push_back(Tab); + AddNewWebTab(a_Title, a_FunctionReference); return true; } diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 70d203244..393737b34 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -176,12 +176,13 @@ public: /** 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 override + // cWebPlugin overrides virtual const AString GetWebTitle(void) const {return GetName(); } + virtual AString HandleWebRequest(const HTTPRequest & a_Request) override; - // cWebPlugin and WebAdmin stuff - virtual AString HandleWebRequest(const HTTPRequest * a_Request) override; - bool AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference); // >> EXPORTED IN MANUALBINDINGS << + /** 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); diff --git a/src/Bindings/WebPlugin.cpp b/src/Bindings/WebPlugin.cpp index 5759b20e7..1eca7de93 100644 --- a/src/Bindings/WebPlugin.cpp +++ b/src/Bindings/WebPlugin.cpp @@ -24,75 +24,82 @@ cWebPlugin::cWebPlugin() cWebPlugin::~cWebPlugin() { + ASSERT(m_Tabs.empty()); // Has ClearTabs() been called? + + // Remove from WebAdmin: cWebAdmin * WebAdmin = cRoot::Get()->GetWebAdmin(); if (WebAdmin != nullptr) { WebAdmin->RemovePlugin(this); } +} + + + - for (TabList::iterator itr = m_Tabs.begin(); itr != m_Tabs.end(); ++itr) + +cWebPlugin::cTabNames cWebPlugin::GetTabNames(void) const +{ + std::list< std::pair> NameList; + for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr) { - delete *itr; + NameList.push_back(std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle)); } - m_Tabs.clear(); + return NameList; } -std::list > cWebPlugin::GetTabNames(void) +cWebPlugin::cTabPtr cWebPlugin::GetTabBySafeTitle(const AString & a_SafeTitle) const { - std::list< std::pair< AString, AString > > NameList; - for (TabList::iterator itr = GetTabs().begin(); itr != GetTabs().end(); ++itr) + cCSLock Lock(m_CSTabs); + for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr) { - std::pair< AString, AString > StringPair; - StringPair.first = (*itr)->Title; - StringPair.second = (*itr)->SafeTitle; - NameList.push_back( StringPair); + if ((*itr)->m_SafeTitle == a_SafeTitle) + { + return *itr; + } } - return NameList; + return nullptr; } -std::pair< AString, AString > cWebPlugin::GetTabNameForRequest(const HTTPRequest * a_Request) +std::pair cWebPlugin::GetTabNameForRequest(const HTTPRequest & a_Request) { - std::pair< AString, AString > Names; - AStringVector Split = StringSplit(a_Request->Path, "/"); + AStringVector Split = StringSplit(a_Request.Path, "/"); + if (Split.empty()) + { + return std::make_pair(AString(), AString()); + } - if (Split.size() > 1) + cCSLock Lock(m_CSTabs); + cTabPtr Tab; + if (Split.size() > 2) // If we got the tab name, show that page { - sWebPluginTab * Tab = nullptr; - 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) { - for (TabList::iterator itr = GetTabs().begin(); itr != GetTabs().end(); ++itr) + if ((*itr)->m_SafeTitle.compare(Split[2]) == 0) // This is the one! { - if ((*itr)->SafeTitle.compare(Split[2]) == 0) // This is the one! - { - Tab = *itr; - break; - } - } - } - else // Otherwise show the first tab - { - if (GetTabs().size() > 0) - { - Tab = *GetTabs().begin(); + return std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle); } } + // Tab name not found, display an "empty" page: + return std::make_pair(AString(), AString()); + } - if (Tab != nullptr) - { - Names.first = Tab->Title; - Names.second = Tab->SafeTitle; - } + // Show the first tab: + if (!m_Tabs.empty()) + { + return std::make_pair(m_Tabs.front()->m_SafeTitle, m_Tabs.front()->m_SafeTitle); } - return Names; + // No tabs at all: + return std::make_pair(AString(), AString()); } @@ -101,14 +108,16 @@ std::pair< AString, AString > cWebPlugin::GetTabNameForRequest(const HTTPRequest AString cWebPlugin::SafeString(const AString & a_String) { AString RetVal; - for (unsigned int i = 0; i < a_String.size(); ++i) + 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); + RetVal.push_back(c); } return RetVal; } @@ -116,3 +125,28 @@ AString cWebPlugin::SafeString(const AString & a_String) + +void cWebPlugin::AddNewWebTab(const AString & a_Title, int a_UserData) +{ + auto Tab = std::make_shared(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); + } +} + + + + diff --git a/src/Bindings/WebPlugin.h b/src/Bindings/WebPlugin.h index 9b825b918..ade4f4359 100644 --- a/src/Bindings/WebPlugin.h +++ b/src/Bindings/WebPlugin.h @@ -12,34 +12,67 @@ 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 cTabPtr; + typedef std::list cTabPtrs; + typedef std::list> 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; - virtual AString HandleWebRequest(const HTTPRequest * a_Request) = 0; + /** Sanitizes the input string, replacing spaces with underscores. */ + static AString SafeString(const AString & a_String); - static AString SafeString( const AString & a_String); // tolua_end - struct sWebPluginTab - { - std::string Title; - std::string SafeTitle; + virtual AString HandleWebRequest(const HTTPRequest & a_Request) = 0; - int UserData; - }; + /** 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); - typedef std::list< sWebPluginTab* > TabList; - TabList & GetTabs() { return m_Tabs; } + /** Returns all the tabs that this plugin has registered. */ + const cTabPtrs & GetTabs(void) const { return m_Tabs; } - typedef std::list< std::pair > TabNameList; - TabNameList GetTabNames(); // >> EXPORTED IN MANUALBINDINGS << - std::pair< AString, AString > GetTabNameForRequest(const HTTPRequest* a_Request); + /** 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 GetTabNameForRequest(const HTTPRequest & a_Request); private: - TabList m_Tabs; + /** 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 diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index ec265ea5b..aff3b4710 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -464,10 +464,10 @@ sWebAdminPage cWebAdmin::GetPage(const HTTPRequest & a_Request) { if ((*itr)->GetWebTitle() == Split[1]) { - Page.Content = (*itr)->HandleWebRequest(&a_Request); + Page.Content = (*itr)->HandleWebRequest(a_Request); cWebPlugin * WebPlugin = *itr; FoundPlugin = WebPlugin->GetWebTitle(); - AString TabName = WebPlugin->GetTabNameForRequest(&a_Request).first; + AString TabName = WebPlugin->GetTabNameForRequest(a_Request).first; Page.PluginName = FoundPlugin; Page.TabName = TabName; break; -- cgit v1.2.3 From 4a946aa8c40b084dd36352e617b24407a2dd537b Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 19 Apr 2015 17:20:15 +0200 Subject: Added cPluginManager:IsPluginLoaded() API, better load error msgs. --- src/Bindings/PluginManager.cpp | 42 ++++++++++++++++++++++++++++++++++++++---- src/Bindings/PluginManager.h | 5 ++++- 2 files changed, 42 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 45a778eda..003996802 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -181,11 +181,29 @@ void cPluginManager::Tick(float a_Dt) cCSLock Lock(m_CSPluginsToUnload); std::swap(m_PluginsToUnload, PluginsToUnload); } - for (auto & plugin: m_Plugins) + for (auto & folder: PluginsToUnload) { - if (std::find(PluginsToUnload.cbegin(), PluginsToUnload.cend(), plugin->GetFolderName()) != PluginsToUnload.cend()) + bool HasUnloaded = false; + bool HasFound = false; + for (auto & plugin: m_Plugins) { - plugin->Unload(); + if (plugin->GetFolderName() == folder) + { + HasFound = true; + if (plugin->IsLoaded()) + { + plugin->Unload(); + HasUnloaded = true; + } + } + } + if (!HasFound) + { + LOG("Cannot unload plugin in folder \"%s\", there's no such plugin folder", folder.c_str()); + } + else if (!HasUnloaded) + { + LOG("Cannot unload plugin in folder \"%s\", it has not been loaded.", folder.c_str()); } } // for plugin - m_Plugins[] @@ -1510,7 +1528,7 @@ bool cPluginManager::LoadPlugin(const AString & a_FolderName) } // for plugin - m_Plugins[] // Plugin not found - LOGD("%s: Plugin folder %s not found in the list of plugins.", __FUNCTION__, a_FolderName.c_str()); + LOG("Cannot load plugin, folder \"%s\" not found.", a_FolderName.c_str()); return false; } @@ -1556,6 +1574,22 @@ void cPluginManager::RemovePluginCommands(cPlugin * a_Plugin) +bool cPluginManager::IsPluginLoaded(const AString & a_PluginName) +{ + for (auto & plugin: m_Plugins) + { + if (plugin->GetName() == a_PluginName) + { + return true; + } + } + return false; +} + + + + + bool cPluginManager::BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) { CommandMap::iterator cmd = m_Commands.find(a_Command); diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index f32feb8a3..994f19943 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -265,7 +265,10 @@ public: /** Removes all command bindings that the specified plugin has made */ void RemovePluginCommands(cPlugin * a_Plugin); - + + /** 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 -- cgit v1.2.3 From be40ea323ad404b4f186a44e60f1926384687e67 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 19 Apr 2015 17:25:48 +0200 Subject: Refresh plugin list before trying to load. --- src/Server.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/Server.cpp b/src/Server.cpp index 9c5c6b6eb..8b6a2e769 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -464,6 +464,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac { if (split.size() > 1) { + cPluginManager::Get()->RefreshPluginList(); // Refresh the plugin list, so that if the plugin was added just now, it is loadable a_Output.Out(cPluginManager::Get()->LoadPlugin(split[1]) ? "Plugin loaded" : "Error occurred loading plugin"); } else -- cgit v1.2.3