From e57b50604ee0e048a9354d342f84cd764e9f8e71 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 13 Apr 2022 12:49:26 -0400 Subject: [PATCH] Add settingsToJson() and settingsFromJson() to root namespace plugin API, allowing plugins to store plugin-wide user settings. --- adapters/standalone.cpp | 1 + include/plugin.hpp | 1 + include/plugin/callbacks.hpp | 16 ++++++++++++ include/settings.hpp | 2 ++ src/plugin.cpp | 48 +++++++++++++++++++++++++++--------- src/settings.cpp | 23 +++++++++++++++++ 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/adapters/standalone.cpp b/adapters/standalone.cpp index 4f8766b7..7db6dfea 100644 --- a/adapters/standalone.cpp +++ b/adapters/standalone.cpp @@ -273,6 +273,7 @@ int main(int argc, char* argv[]) { plugin::destroy(); INFO("Destroying network"); network::destroy(); + settings::destroy(); INFO("Destroying logger"); logger::destroy(); diff --git a/include/plugin.hpp b/include/plugin.hpp index 6adacda8..6a09996d 100644 --- a/include/plugin.hpp +++ b/include/plugin.hpp @@ -13,6 +13,7 @@ namespace plugin { PRIVATE void init(); PRIVATE void destroy(); +PRIVATE void settingsMergeJson(json_t* rootJ); /** Finds a loaded Plugin by slug. */ Plugin* getPlugin(const std::string& pluginSlug); diff --git a/include/plugin/callbacks.hpp b/include/plugin/callbacks.hpp index 5c53c09d..300514ca 100644 --- a/include/plugin/callbacks.hpp +++ b/include/plugin/callbacks.hpp @@ -16,3 +16,19 @@ Optional in plugins. */ extern "C" void destroy(); + +/** Called when saving user settings. +Stored in `settings["pluginSettings"][pluginSlug]`. +Useful for persisting plugin-wide settings. + +Optional in plugins. +*/ +extern "C" +json_t* settingsToJson(); + +/** Called after initializing plugin if user plugin settings property is defined. + +Optional in plugins. +*/ +extern "C" +void settingsFromJson(json_t* rootJ); diff --git a/include/settings.hpp b/include/settings.hpp index 2b7c1a9b..75516078 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -83,6 +83,7 @@ enum BrowserSort { }; extern BrowserSort browserSort; extern float browserZoom; +extern json_t* pluginSettingsJ; struct ModuleInfo { bool enabled = true; @@ -110,6 +111,7 @@ extern std::map moduleWhitelist; bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug); PRIVATE void init(); +PRIVATE void destroy(); PRIVATE json_t* toJson(); PRIVATE void fromJson(json_t* rootJ); PRIVATE void save(std::string path = ""); diff --git a/src/plugin.cpp b/src/plugin.cpp index f52e16ea..3b61b215 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -41,6 +41,18 @@ namespace plugin { // private API //////////////////// + +static void* getSymbol(void* handle, const char* name) { + if (!handle) + return NULL; + +#if defined ARCH_WIN + return GetProcAddress((HMODULE) handle, name); +#else + return dlsym(handle, name); +#endif +} + /** Returns library handle */ static void* loadLibrary(std::string libraryPath) { #if defined ARCH_WIN @@ -100,12 +112,7 @@ static InitCallback loadPluginCallback(Plugin* plugin) { plugin->handle = loadLibrary(libraryPath); // Get plugin's init() function - InitCallback initCallback; -#if defined ARCH_WIN - initCallback = (InitCallback) GetProcAddress((HMODULE) plugin->handle, "init"); -#else - initCallback = (InitCallback) dlsym(plugin->handle, "init"); -#endif + InitCallback initCallback = (InitCallback) getSymbol(plugin->handle, "init"); if (!initCallback) throw Exception("Failed to read init() symbol in %s", libraryPath.c_str()); @@ -169,6 +176,15 @@ static Plugin* loadPlugin(std::string path) { Plugin* existingPlugin = getPlugin(plugin->slug); if (existingPlugin) throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()); + + // Call settingsFromJson() if exists + // Returns NULL for Core. + auto settingsFromJson = (decltype(&::settingsFromJson)) getSymbol(plugin->handle, "settingsFromJson"); + if (settingsFromJson) { + json_t* settingsJ = json_object_get(settings::pluginSettingsJ, plugin->slug.c_str()); + if (settingsJ) + settingsFromJson(settingsJ); + } } catch (Exception& e) { WARN("Could not load plugin %s: %s", path.c_str(), e.what()); @@ -265,11 +281,7 @@ static void destroyPlugin(Plugin* plugin) { typedef void (*DestroyCallback)(); DestroyCallback destroyCallback = NULL; if (handle) { -#if defined ARCH_WIN - destroyCallback = (DestroyCallback) GetProcAddress((HMODULE) handle, "destroy"); -#else - destroyCallback = (DestroyCallback) dlsym(handle, "destroy"); -#endif + destroyCallback = (DestroyCallback) getSymbol(handle, "destroy"); } if (destroyCallback) { try { @@ -303,6 +315,20 @@ void destroy() { } +void settingsMergeJson(json_t* rootJ) { + for (Plugin* plugin : plugins) { + auto settingsToJson = (decltype(&::settingsToJson)) getSymbol(plugin->handle, "settingsToJson"); + if (settingsToJson) { + json_t* settingsJ = settingsToJson(); + json_object_set_new(rootJ, plugin->slug.c_str(), settingsJ); + } + else { + json_object_del(rootJ, plugin->slug.c_str()); + } + } +} + + /** Given slug => fallback slug. Correctly handles bidirectional fallbacks. To request fallback slugs to be added to this list, open a GitHub issue. diff --git a/src/settings.cpp b/src/settings.cpp index 1fb91691..dd6c7d18 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -61,6 +61,7 @@ int tipIndex = -1; bool discordUpdateActivity = true; BrowserSort browserSort = BROWSER_SORT_UPDATED; float browserZoom = -1.f; +json_t* pluginSettingsJ = NULL; std::map> moduleInfos; std::map moduleWhitelist; @@ -98,6 +99,12 @@ void init() { } +void destroy() { + if (pluginSettingsJ) + json_decref(pluginSettingsJ); +} + + json_t* toJson() { json_t* rootJ = json_object(); @@ -176,6 +183,13 @@ json_t* toJson() { json_object_set_new(rootJ, "browserZoom", json_real(browserZoom)); + // Merge pluginSettings instead of replace so plugins that fail to load don't cause their settings to be deleted. + if (!pluginSettingsJ) + pluginSettingsJ = json_object(); + plugin::settingsMergeJson(pluginSettingsJ); + // Don't use *_set_new() here because we need to keep the reference to pluginSettingsJ. + json_object_set(rootJ, "pluginSettings", pluginSettingsJ); + // moduleInfos json_t* moduleInfosJ = json_object(); for (const auto& pluginPair : moduleInfos) { @@ -375,6 +389,15 @@ void fromJson(json_t* rootJ) { if (browserZoomJ) browserZoom = json_number_value(browserZoomJ); + // Delete previous pluginSettings object + if (pluginSettingsJ) { + json_decref(pluginSettingsJ); + pluginSettingsJ = NULL; + } + pluginSettingsJ = json_object_get(rootJ, "pluginSettings"); + if (pluginSettingsJ) + json_incref(pluginSettingsJ); + moduleInfos.clear(); json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos"); if (moduleInfosJ) {