diff --git a/bin/dbus/cx.ring.Ring.PluginManagerInterface.xml b/bin/dbus/cx.ring.Ring.PluginManagerInterface.xml index 5dd8e9d7fe0cd5bb3a955ea4892d5864b89e2527..cacbb47e8cf484172fa811bc264c8877b3d2254f 100644 --- a/bin/dbus/cx.ring.Ring.PluginManagerInterface.xml +++ b/bin/dbus/cx.ring.Ring.PluginManagerInterface.xml @@ -154,5 +154,16 @@ </arg> </method> + <method name="addValueToPreference" tp:name-for-bindings="addValueToPreference"> + <tp:added version="9.6.0"/> + <arg type="s" name="pluginId" direction="in"> + </arg> + <arg type="s" name="preferenceKey" direction="in"> + </arg> + <arg type="s" name="value" direction="in"> + </arg> + <arg type="b" name="status" direction="out"> + </arg> + </method> </interface> </node> diff --git a/bin/dbus/dbuspluginmanagerinterface.cpp b/bin/dbus/dbuspluginmanagerinterface.cpp index 9bc0b098cb91ab7b36c427fe2a0eac5a1d9a56c1..8b63ed47057f19f8a73d0c98fa55023150090707 100644 --- a/bin/dbus/dbuspluginmanagerinterface.cpp +++ b/bin/dbus/dbuspluginmanagerinterface.cpp @@ -132,3 +132,11 @@ DBusPluginManagerInterface::getCallMediaHandlerStatus() { return DRing::getCallMediaHandlerStatus(); } + +bool +DBusPluginManagerInterface::addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value) +{ + return DRing::addValueToPreference(pluginId, preferenceKey, value); +} diff --git a/bin/dbus/dbuspluginmanagerinterface.h b/bin/dbus/dbuspluginmanagerinterface.h index 9fd353660adf5e6ca5bf359b6ab48e26052fd8a6..4ebaeb6dbd9f7fe09b03493afb1882ad5e025f68 100644 --- a/bin/dbus/dbuspluginmanagerinterface.h +++ b/bin/dbus/dbuspluginmanagerinterface.h @@ -71,4 +71,7 @@ class DRING_PUBLIC DBusPluginManagerInterface : bool getPluginsEnabled(); void setPluginsEnabled(const bool& state); std::map<std::string,std::string> getCallMediaHandlerStatus(); + bool addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value); }; diff --git a/bin/jni/plugin_manager_interface.i b/bin/jni/plugin_manager_interface.i index 330c5c8dd54765a328dc66ae42f57909c28d945c..d995f67d8f1171e4e76b7aede1e82338f4075d71 100644 --- a/bin/jni/plugin_manager_interface.i +++ b/bin/jni/plugin_manager_interface.i @@ -42,4 +42,7 @@ std::map<std::string,std::string> getCallMediaHandlerDetails(const std::string& bool getPluginsEnabled(); void setPluginsEnabled(bool state); std::map<std::string,std::string> getCallMediaHandlerStatus(); +bool addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value); } diff --git a/configure.ac b/configure.ac index 2efeab8971d40ead044420c0b2bb10da01c19941..65e785d1cf0537fcd24d3d970a05329cf9b655db 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac for automake 1.9 and autoconf 2.59 dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([Jami Daemon],[9.5.0],[ring@gnu.org],[jami]) +AC_INIT([Jami Daemon],[9.6.0],[ring@gnu.org],[jami]) AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2020]]) AC_REVISION([$Revision$]) diff --git a/meson.build b/meson.build index 9f49b57ba3e78382bc5c56466766372f870db601..86b0643e0939b7242fd342b9ecb79e1292d08451 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '9.5.0', + version: '9.6.0', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.54' diff --git a/src/client/plugin_manager_interface.cpp b/src/client/plugin_manager_interface.cpp index 3e28ac6ca18f6529375ae2c91cc8e49e6d94b1a9..97acea27728361e16b86f0eecbd28c9e814f2a4e 100644 --- a/src/client/plugin_manager_interface.cpp +++ b/src/client/plugin_manager_interface.cpp @@ -144,4 +144,14 @@ getCallMediaHandlerStatus() .getCallServicesManager() .getCallMediaHandlerStatus(); } + +bool +addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value) +{ + return jami::Manager::instance().getJamiPluginManager().addValueToPreference(pluginId, + preferenceKey, + value); +} } // namespace DRing diff --git a/src/dring/plugin_manager_interface.h b/src/dring/plugin_manager_interface.h index e99a5459f1cd063508ab5ce7f464b95e094a8429..01570212d82177b63f546e8cf32a4ae35cd1aaa7 100644 --- a/src/dring/plugin_manager_interface.h +++ b/src/dring/plugin_manager_interface.h @@ -53,4 +53,7 @@ DRING_PUBLIC std::map<std::string,std::string> getCallMediaHandlerDetails(const DRING_PUBLIC bool getPluginsEnabled(); DRING_PUBLIC void setPluginsEnabled(bool state); DRING_PUBLIC std::map<std::string,std::string> getCallMediaHandlerStatus(); +DRING_PUBLIC bool addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value); } diff --git a/src/fileutils.cpp b/src/fileutils.cpp index 81e2290cf98c898d01c56b52c005fdf66c1fe3ff..b1576fba85018ee5c7445213aba661fe674ff516 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -1023,5 +1023,35 @@ accessFile(const std::string& file, int mode) #endif } +std::string +getFileName(const std::string& filePath) +{ + std::string fileName = filePath; + const size_t last_slash_idx = fileName.find_last_of(DIR_SEPARATOR_STR_ESC); + if (std::string::npos != last_slash_idx) { + fileName.erase(0, last_slash_idx + 1); + } + return fileName; +} + +std::string +removeExtension(const std::string& filePath) +{ + std::string fileName = filePath; + const size_t period_idx = fileName.rfind('.'); + if (std::string::npos != period_idx) { + fileName.erase(period_idx); + } + return fileName; +} + +std::string +getExtension(const std::string& filePath) +{ + std::string fileExt = filePath; + fileExt = fileExt.substr(fileExt.find_last_of('.')); + return fileExt; +} + } // namespace jami } // namespace fileutils diff --git a/src/fileutils.h b/src/fileutils.h index 57e424c31eb5a34a54ec3b399b76093f440068e5..9f1c54a52be8f0d4a83eb51397344d3de9a519de 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -176,6 +176,10 @@ std::string md5sum(const std::vector<uint8_t>& buffer); */ int accessFile(const std::string& file, int mode); +std::string getFileName(const std::string& filePath); +std::string removeExtension(const std::string& filePath); +std::string getExtension(const std::string& filePath); + } // namespace fileutils } // namespace jami diff --git a/src/plugin/jamipluginmanager.cpp b/src/plugin/jamipluginmanager.cpp index 69d84e403d7ff73246fce96bb16d471025506277..dd16401e527056f5442ac7ca86fd588fd93e5448 100644 --- a/src/plugin/jamipluginmanager.cpp +++ b/src/plugin/jamipluginmanager.cpp @@ -34,6 +34,7 @@ extern "C" { #include <archive.h> } +#include "fileutils.h" #include <json/json.h> #include <msgpack.hpp> @@ -337,11 +338,10 @@ JamiPluginManager::unloadPlugin(const std::string& rootPath) void JamiPluginManager::togglePlugin(const std::string& rootPath, bool toggle) { - //This function should not be used as is - //One should modify it to perform plugin install followed by load - //rootPath should be the jplpath! - try - { + // This function should not be used as is + // One should modify it to perform plugin install followed by load + // rootPath should be the jplpath! + try { std::string soPath = getPluginDetails(rootPath).at("soPath"); // remove the previous plugin object if it was registered pm_.destroyPluginComponents(soPath); @@ -372,6 +372,8 @@ std::vector<std::map<std::string, std::string>> JamiPluginManager::getPluginPreferences(const std::string& rootPath) { const std::string preferenceFilePath = getPreferencesConfigFilePath(rootPath); + std::map<std::string, std::map<std::string, std::string>> userPreferences + = getUserPreferencesValuesMap(rootPath); std::ifstream file(preferenceFilePath); Json::Value root; Json::CharReaderBuilder rbuilder; @@ -389,11 +391,34 @@ JamiPluginManager::getPluginPreferences(const std::string& rootPath) std::string key = jsonPreference.get("key", "None").asString(); if (type != "None" && key != "None") { if (keys.find(key) == keys.end()) { - const auto& preferenceAttributes = parsePreferenceConfig(jsonPreference, - type); + std::map<std::string, std::string> preferenceAttributes + = parsePreferenceConfig(jsonPreference, type); // If the parsing of the attributes was successful, commit the map and the key if (!preferenceAttributes.empty()) { - preferences.push_back(std::move(preferenceAttributes)); + if (!userPreferences[key].empty()) { + preferenceAttributes["entryValues"] + = userPreferences[key]["entryValues"]; + preferenceAttributes["entries"] = userPreferences[key]["entries"]; + } + + preferenceAttributes["entryValues"] + = std::regex_replace(preferenceAttributes["entryValues"], + std::regex("\\["), + "$2"); + preferenceAttributes["entryValues"] + = std::regex_replace(preferenceAttributes["entryValues"], + std::regex("\\]"), + "$2"); + preferenceAttributes["entries"] + = std::regex_replace(preferenceAttributes["entries"], + std::regex("\\["), + "$2"); + preferenceAttributes["entries"] + = std::regex_replace(preferenceAttributes["entries"], + std::regex("\\]"), + "$2"); + + preferences.emplace_back(std::move(preferenceAttributes)); keys.insert(key); } } @@ -443,6 +468,41 @@ JamiPluginManager::getPluginUserPreferencesValuesMap(const std::string& rootPath return rmap; } +std::map<std::string, std::map<std::string, std::string>> +JamiPluginManager::getUserPreferencesValuesMap(const std::string& rootPath) +{ + const std::string preferencesValuesFilePath = pluginAddedPreferencesValuesFilePath(rootPath); + std::ifstream file(preferencesValuesFilePath, std::ios::binary); + std::map<std::string, std::map<std::string, std::string>> rmap; + + // If file is accessible + if (file.good()) { + std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath)); + // Get file size + std::string str; + file.seekg(0, std::ios::end); + size_t fileSize = static_cast<size_t>(file.tellg()); + // If not empty + if (fileSize > 0) { + // Read whole file content and put it in the string str + str.reserve(static_cast<size_t>(file.tellg())); + file.seekg(0, std::ios::beg); + str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); + file.close(); + try { + // Unpack the string + msgpack::object_handle oh = msgpack::unpack(str.data(), str.size()); + // Deserialized object is valid during the msgpack::object_handle instance is alive. + msgpack::object deserialized = oh.get(); + deserialized.convert(rmap); + } catch (const std::exception& e) { + JAMI_ERR() << e.what(); + } + } + } + return rmap; +} + bool JamiPluginManager::setPluginPreference(const std::string& rootPath, const std::string& key, @@ -521,6 +581,69 @@ JamiPluginManager::resetPluginPreferencesValuesMap(const std::string& rootPath) return returnValue; } +bool +JamiPluginManager::copyFileToPluginData(const std::string& pluginId, + const std::string& value, + const std::string& preferenceCategory, + std::string& fileName, + std::string& fileExt) +{ + if (!fileutils::isFile(value)) + return false; + + const std::string destinationDir {pluginId + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + + preferenceCategory + DIR_SEPARATOR_CH}; + fileName = fileutils::removeExtension(fileutils::getFileName(value)); + fileExt = fileutils::getExtension(value); + + auto srcData = fileutils::loadFile(value); + + if (fileutils::isFile(destinationDir + fileName + fileExt)) { + fileutils::saveFile(destinationDir + fileName + fileExt, srcData); + return false; + } + fileutils::saveFile(destinationDir + fileName + fileExt, srcData); + return true; +} + +bool +JamiPluginManager::addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value) +{ + std::map<std::string, std::map<std::string, std::string>> userPreferences + = getUserPreferencesValuesMap(pluginId); + std::vector<std::map<std::string, std::string>> preferences = getPluginPreferences(pluginId); + + for (auto& preference : preferences) { + if (preference["key"] == preferenceKey) { + std::string fileName, fileExt; + if (!copyFileToPluginData(pluginId, value, preference["category"], fileName, fileExt)) { + return setPluginPreference(pluginId, preferenceKey, fileName + fileExt); + } + setPluginPreference(pluginId, preferenceKey, fileName + fileExt); + userPreferences[preferenceKey]["entries"] = preference["entries"] + "," + fileName; + userPreferences[preferenceKey]["entryValues"] = preference["entryValues"] + "," + + fileName + fileExt; + + const std::string preferencesValuesFilePath = pluginAddedPreferencesValuesFilePath( + pluginId); + std::ofstream fs(preferencesValuesFilePath, std::ios::binary); + if (!fs.good()) { + return false; + } + try { + std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath)); + msgpack::pack(fs, userPreferences); + return true; + } catch (const std::exception& e) { + JAMI_ERR() << e.what(); + return false; + } + } + } +} + std::map<std::string, std::string> JamiPluginManager::readPluginManifestFromArchive(const std::string& jplPath) { diff --git a/src/plugin/jamipluginmanager.h b/src/plugin/jamipluginmanager.h index fb37398d38c75e3450c9168ab104088e583834ae..d3c9ad15843ef983c24d6090938efcef99efb78d 100644 --- a/src/plugin/jamipluginmanager.h +++ b/src/plugin/jamipluginmanager.h @@ -125,6 +125,10 @@ public: bool resetPluginPreferencesValuesMap(const std::string& rootPath); + bool addValueToPreference(const std::string& pluginId, + const std::string& preferenceKey, + const std::string& value); + public: CallServicesManager& getCallServicesManager() { return csm_; } @@ -180,6 +184,13 @@ private: } std::map<std::string, std::string> getPluginUserPreferencesValuesMap(const std::string& rootPath); + std::map<std::string, std::map<std::string, std::string>> getUserPreferencesValuesMap( + const std::string& rootPath); + bool copyFileToPluginData(const std::string& pluginId, + const std::string& value, + const std::string& preferenceCategory, + std::string& fileName, + std::string& fileExt); /** * @brief getPreferencesConfigFilePath @@ -205,6 +216,18 @@ private: return rootPath + DIR_SEPARATOR_CH + "preferences.msgpack"; } + /** + * @brief pluginAddedPreferencesValuesFilePath + * Returns the plugin added preferences values file path from the plugin root path + * This is entirely defined by how the plugin files are structured + * @param plugin rootPath + * @return path of the preferences values + */ + std::string pluginAddedPreferencesValuesFilePath(const std::string& rootPath) const + { + return rootPath + DIR_SEPARATOR_CH + "addedPreferences.msgpack"; + } + void registerServices(); private: diff --git a/src/plugin/mediahandler.h b/src/plugin/mediahandler.h index 422a7991348f13d31512194dfe49ab427ba85d59..7a25457da3f8e03981ecf6663b05b702a5b0b6b2 100644 --- a/src/plugin/mediahandler.h +++ b/src/plugin/mediahandler.h @@ -40,8 +40,8 @@ public: * The id is the path of the plugin that created this MediaHandler * @return */ - std::string id() const { return id_;} - virtual void setId(const std::string& id) final {id_ = id;} + std::string id() const { return id_; } + virtual void setId(const std::string& id) final { id_ = id; } private: std::string id_;