From 7f215008662df71de2e922a9b7417345a081777e Mon Sep 17 00:00:00 2001
From: agsantos <aline.gondimsantos@savoirfairelinux.com>
Date: Mon, 24 Aug 2020 21:42:41 -0400
Subject: [PATCH] plugin: add editable preference

Change-Id: I61c074464e21344b2d7a1d64023f23bd96139c3b
---
 .../cx.ring.Ring.PluginManagerInterface.xml   |  11 ++
 bin/dbus/dbuspluginmanagerinterface.cpp       |   8 +
 bin/dbus/dbuspluginmanagerinterface.h         |   3 +
 bin/jni/plugin_manager_interface.i            |   3 +
 configure.ac                                  |   2 +-
 meson.build                                   |   2 +-
 src/client/plugin_manager_interface.cpp       |  10 ++
 src/dring/plugin_manager_interface.h          |   3 +
 src/fileutils.cpp                             |  30 ++++
 src/fileutils.h                               |   4 +
 src/plugin/jamipluginmanager.cpp              | 139 +++++++++++++++++-
 src/plugin/jamipluginmanager.h                |  23 +++
 src/plugin/mediahandler.h                     |   4 +-
 13 files changed, 230 insertions(+), 12 deletions(-)

diff --git a/bin/dbus/cx.ring.Ring.PluginManagerInterface.xml b/bin/dbus/cx.ring.Ring.PluginManagerInterface.xml
index 5dd8e9d7fe..cacbb47e8c 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 9bc0b098cb..8b63ed4705 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 9fd353660a..4ebaeb6dbd 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 330c5c8dd5..d995f67d8f 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 2efeab8971..65e785d1cf 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 9f49b57ba3..86b0643e09 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 3e28ac6ca1..97acea2772 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 e99a5459f1..01570212d8 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 81e2290cf9..b1576fba85 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 57e424c31e..9f1c54a52b 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 69d84e403d..dd16401e52 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 fb37398d38..d3c9ad1584 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 422a799134..7a25457da3 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_;
-- 
GitLab