LCOV - code coverage report
Current view: top level - src/plugin - pluginpreferencesutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 178 224 79.5 %
Date: 2024-04-24 08:04:06 Functions: 12 13 92.3 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2020-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
       5             :  *
       6             :  *  This program is free software; you can redistribute it and/or modify
       7             :  *  it under the terms of the GNU General Public License as published by
       8             :  *  the Free Software Foundation; either version 3 of the License, or
       9             :  *  (at your option) any later version.
      10             :  *
      11             :  *  This program is distributed in the hope that it will be useful,
      12             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :  *  GNU General Public License for more details.
      15             :  *
      16             :  *  You should have received a copy of the GNU General Public License
      17             :  *  along with this program; if not, write to the Free Software
      18             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
      19             :  * USA.
      20             :  */
      21             : 
      22             : #include "pluginpreferencesutils.h"
      23             : #include "pluginsutils.h"
      24             : 
      25             : #include <msgpack.hpp>
      26             : #include <sstream>
      27             : #include <fstream>
      28             : #include <fmt/core.h>
      29             : 
      30             : #include "logger.h"
      31             : #include "fileutils.h"
      32             : 
      33             : namespace jami {
      34             : 
      35             : std::filesystem::path
      36         165 : PluginPreferencesUtils::getPreferencesConfigFilePath(const std::filesystem::path& rootPath,
      37             :                                                      const std::string& accountId)
      38             : {
      39         165 :     if (accountId.empty())
      40         180 :         return rootPath / "data" / "preferences.json";
      41             :     else
      42         150 :         return rootPath / "data" / "accountpreferences.json";
      43             : }
      44             : 
      45             : std::filesystem::path
      46          74 : PluginPreferencesUtils::valuesFilePath(const std::filesystem::path& rootPath, const std::string& accountId)
      47             : {
      48          74 :     if (accountId.empty() || accountId == "default")
      49          41 :         return rootPath / "preferences.msgpack";
      50          33 :     auto pluginName = rootPath.filename();
      51          66 :     auto dir = fileutils::get_data_dir() / accountId / "plugins" / pluginName;
      52          33 :     dhtnet::fileutils::check_dir(dir);
      53          33 :     return dir / "preferences.msgpack";
      54          33 : }
      55             : 
      56             : std::filesystem::path
      57          35 : PluginPreferencesUtils::getAllowDenyListsPath()
      58             : {
      59          70 :     return fileutils::get_data_dir() / "plugins" / "allowdeny.msgpack";
      60             : }
      61             : 
      62             : std::string
      63           0 : PluginPreferencesUtils::convertArrayToString(const Json::Value& jsonArray)
      64             : {
      65           0 :     std::string stringArray {};
      66             : 
      67           0 :     if (jsonArray.size()) {
      68           0 :         for (unsigned i = 0; i < jsonArray.size() - 1; i++) {
      69           0 :             if (jsonArray[i].isString()) {
      70           0 :                 stringArray += jsonArray[i].asString() + ",";
      71           0 :             } else if (jsonArray[i].isArray()) {
      72           0 :                 stringArray += convertArrayToString(jsonArray[i]) + ",";
      73             :             }
      74             :         }
      75             : 
      76           0 :         unsigned lastIndex = jsonArray.size() - 1;
      77           0 :         if (jsonArray[lastIndex].isString()) {
      78           0 :             stringArray += jsonArray[lastIndex].asString();
      79             :         }
      80             :     }
      81             : 
      82           0 :     return stringArray;
      83           0 : }
      84             : 
      85             : std::map<std::string, std::string>
      86         270 : PluginPreferencesUtils::parsePreferenceConfig(const Json::Value& jsonPreference)
      87             : {
      88         270 :     std::map<std::string, std::string> preferenceMap;
      89         270 :     const auto& members = jsonPreference.getMemberNames();
      90             :     // Insert other fields
      91        1773 :     for (const auto& member : members) {
      92        1503 :         const Json::Value& value = jsonPreference[member];
      93        1503 :         if (value.isString()) {
      94        1503 :             preferenceMap.emplace(member, jsonPreference[member].asString());
      95           0 :         } else if (value.isArray()) {
      96           0 :             preferenceMap.emplace(member, convertArrayToString(jsonPreference[member]));
      97             :         }
      98             :     }
      99         540 :     return preferenceMap;
     100         270 : }
     101             : 
     102             : std::vector<std::map<std::string, std::string>>
     103         117 : PluginPreferencesUtils::getPreferences(const std::filesystem::path& rootPath, const std::string& accountId)
     104             : {
     105         117 :     auto preferenceFilePath = getPreferencesConfigFilePath(rootPath, accountId);
     106         117 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(preferenceFilePath));
     107         117 :     std::ifstream file(preferenceFilePath);
     108         117 :     Json::Value root;
     109         117 :     Json::CharReaderBuilder rbuilder;
     110         117 :     rbuilder["collectComments"] = false;
     111         117 :     std::string errs;
     112         117 :     std::set<std::string> keys;
     113         117 :     std::vector<std::map<std::string, std::string>> preferences;
     114         117 :     if (file) {
     115             :         // Get preferences locale
     116         117 :         const auto& lang = PluginUtils::getLanguage();
     117         234 :         auto locales = PluginUtils::getLocales(rootPath.string(), std::string(string_remove_suffix(lang, '.')));
     118             : 
     119             :         // Read the file to a json format
     120         117 :         bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
     121         117 :         if (ok && root.isArray()) {
     122             :             // Read each preference described in preference.json individually
     123         387 :             for (unsigned i = 0; i < root.size(); i++) {
     124         270 :                 const Json::Value& jsonPreference = root[i];
     125         540 :                 std::string category = jsonPreference.get("category", "NoCategory").asString();
     126         540 :                 std::string type = jsonPreference.get("type", "None").asString();
     127         540 :                 std::string key = jsonPreference.get("key", "None").asString();
     128             :                 // The preference must have at least type and key
     129         270 :                 if (type != "None" && key != "None") {
     130         270 :                     if (keys.find(key) == keys.end()) {
     131             :                         // Read the rest of the preference
     132         270 :                         auto preferenceAttributes = parsePreferenceConfig(jsonPreference);
     133             :                         // If the parsing of the attributes was successful, commit the map and the keys
     134         270 :                         auto defaultValue = preferenceAttributes.find("defaultValue");
     135         270 :                         if (type == "Path" && defaultValue != preferenceAttributes.end()) {
     136             :                             // defaultValue in a Path preference is an incomplete path
     137             :                             // starting from the installation path of the plugin.
     138             :                             // Here we complete the path value.
     139           0 :                             defaultValue->second = (rootPath / defaultValue->second).string();
     140             :                         }
     141             : 
     142         270 :                         if (!preferenceAttributes.empty()) {
     143        2160 :                             for (const auto& locale : locales) {
     144       12411 :                                 for (auto& pair : preferenceAttributes) {
     145       10521 :                                     string_replace(pair.second,
     146       21042 :                                                    "{{" + locale.first + "}}",
     147       10521 :                                                    locale.second);
     148             :                                 }
     149             :                             }
     150         270 :                             preferences.push_back(std::move(preferenceAttributes));
     151         270 :                             keys.insert(key);
     152             :                         }
     153         270 :                     }
     154             :                 }
     155         270 :             }
     156             :         } else {
     157           0 :             JAMI_ERR() << "PluginPreferencesParser:: Failed to parse preferences.json for plugin: "
     158           0 :                        << preferenceFilePath;
     159             :         }
     160         117 :     }
     161             : 
     162         234 :     return preferences;
     163         117 : }
     164             : 
     165             : std::map<std::string, std::string>
     166          69 : PluginPreferencesUtils::getUserPreferencesValuesMap(const std::filesystem::path& rootPath,
     167             :                                                     const std::string& accountId)
     168             : {
     169          69 :     auto preferencesValuesFilePath = valuesFilePath(rootPath, accountId);
     170          69 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
     171          69 :     std::ifstream file(preferencesValuesFilePath, std::ios::binary);
     172          69 :     std::map<std::string, std::string> rmap;
     173             : 
     174             :     // If file is accessible
     175          69 :     if (file.good()) {
     176             :         // Get file size
     177          15 :         std::string str;
     178          15 :         file.seekg(0, std::ios::end);
     179          15 :         size_t fileSize = static_cast<size_t>(file.tellg());
     180             :         // If not empty
     181          15 :         if (fileSize > 0) {
     182             :             // Read whole file content and put it in the string str
     183          15 :             str.reserve(static_cast<size_t>(file.tellg()));
     184          15 :             file.seekg(0, std::ios::beg);
     185          15 :             str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
     186          15 :             file.close();
     187             :             try {
     188             :                 // Unpack the string
     189          15 :                 msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
     190             :                 // Deserialized object is valid during the msgpack::object_handle instance is alive.
     191          15 :                 msgpack::object deserialized = oh.get();
     192          15 :                 deserialized.convert(rmap);
     193          15 :             } catch (const std::exception& e) {
     194           0 :                 JAMI_ERR() << e.what();
     195           0 :             }
     196             :         }
     197          15 :     }
     198         138 :     return rmap;
     199          69 : }
     200             : 
     201             : std::map<std::string, std::string>
     202          37 : PluginPreferencesUtils::getPreferencesValuesMap(const std::filesystem::path& rootPath,
     203             :                                                 const std::string& accountId)
     204             : {
     205          37 :     std::map<std::string, std::string> rmap;
     206             : 
     207             :     // Read all preferences values
     208          74 :     std::vector<std::map<std::string, std::string>> preferences = getPreferences(rootPath);
     209          37 :     auto accPrefs = getPreferences(rootPath, accountId);
     210         164 :     for (const auto& item : accPrefs) {
     211         127 :         preferences.push_back(item);
     212             :     }
     213         201 :     for (auto& preference : preferences) {
     214         164 :         rmap[preference["key"]] = preference["defaultValue"];
     215             :     }
     216             : 
     217             :     // If any of these preferences were modified, its value is changed before return
     218          39 :     for (const auto& pair : getUserPreferencesValuesMap(rootPath)) {
     219           2 :         rmap[pair.first] = pair.second;
     220          37 :     }
     221             : 
     222          37 :     if (!accountId.empty()) {
     223             :         // If any of these preferences were modified, its value is changed before return
     224          32 :         for (const auto& pair : getUserPreferencesValuesMap(rootPath, accountId)) {
     225           2 :             rmap[pair.first] = pair.second;
     226          30 :         }
     227             :     }
     228             : 
     229          74 :     return rmap;
     230          37 : }
     231             : 
     232             : bool
     233           3 : PluginPreferencesUtils::resetPreferencesValuesMap(const std::string& rootPath,
     234             :                                                   const std::string& accountId)
     235             : {
     236           3 :     bool returnValue = true;
     237           3 :     std::map<std::string, std::string> pluginPreferencesMap {};
     238             : 
     239           3 :     auto preferencesValuesFilePath = valuesFilePath(rootPath, accountId);
     240           3 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
     241           3 :     std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
     242           3 :     if (!fs.good()) {
     243           0 :         return false;
     244             :     }
     245             :     try {
     246           3 :         msgpack::pack(fs, pluginPreferencesMap);
     247           0 :     } catch (const std::exception& e) {
     248           0 :         returnValue = false;
     249           0 :         JAMI_ERR() << e.what();
     250           0 :     }
     251             : 
     252           3 :     return returnValue;
     253           3 : }
     254             : 
     255             : void
     256           2 : PluginPreferencesUtils::setAllowDenyListPreferences(const ChatHandlerList& list)
     257             : {
     258           2 :     auto filePath = getAllowDenyListsPath();
     259           2 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     260           2 :     std::ofstream fs(filePath, std::ios::binary);
     261           2 :     if (!fs.good()) {
     262           0 :         return;
     263             :     }
     264             :     try {
     265           2 :         msgpack::pack(fs, list);
     266           0 :     } catch (const std::exception& e) {
     267           0 :         JAMI_ERR() << e.what();
     268           0 :     }
     269           2 : }
     270             : 
     271             : void
     272          33 : PluginPreferencesUtils::getAllowDenyListPreferences(ChatHandlerList& list)
     273             : {
     274          33 :     auto filePath = getAllowDenyListsPath();
     275          33 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     276          33 :     std::ifstream file(filePath, std::ios::binary);
     277             : 
     278             :     // If file is accessible
     279          33 :     if (file.good()) {
     280             :         // Get file size
     281           0 :         std::string str;
     282           0 :         file.seekg(0, std::ios::end);
     283           0 :         size_t fileSize = static_cast<size_t>(file.tellg());
     284             :         // If not empty
     285           0 :         if (fileSize > 0) {
     286             :             // Read whole file content and put it in the string str
     287           0 :             str.reserve(static_cast<size_t>(file.tellg()));
     288           0 :             file.seekg(0, std::ios::beg);
     289           0 :             str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
     290           0 :             file.close();
     291             :             try {
     292             :                 // Unpack the string
     293           0 :                 msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
     294             :                 // Deserialized object is valid during the msgpack::object_handle instance is alive.
     295           0 :                 msgpack::object deserialized = oh.get();
     296           0 :                 deserialized.convert(list);
     297           0 :             } catch (const std::exception& e) {
     298           0 :                 JAMI_ERR() << e.what();
     299           0 :             }
     300             :         }
     301           0 :     }
     302          33 : }
     303             : 
     304             : void
     305          24 : PluginPreferencesUtils::addAlwaysHandlerPreference(const std::string& handlerName,
     306             :                                                    const std::string& rootPath)
     307             : {
     308             :     {
     309          48 :         auto filePath = getPreferencesConfigFilePath(rootPath);
     310          24 :         Json::Value root;
     311             : 
     312          24 :         std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     313          24 :         std::ifstream file(filePath);
     314          24 :         Json::CharReaderBuilder rbuilder;
     315          24 :         Json::Value preference;
     316          24 :         rbuilder["collectComments"] = false;
     317          24 :         std::string errs;
     318          24 :         if (file) {
     319          24 :             bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
     320          24 :             if (ok && root.isArray()) {
     321             :                 // Return if preference already exists
     322          48 :                 for (const auto& child : root)
     323          24 :                     if (child.get("key", "None").asString() == handlerName + "Always")
     324           0 :                         return;
     325             :             }
     326             :         }
     327          24 :     }
     328             : 
     329          48 :     auto filePath = getPreferencesConfigFilePath(rootPath, "acc");
     330          24 :     Json::Value root;
     331             :     {
     332          24 :         std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     333          24 :         std::ifstream file(filePath);
     334          24 :         Json::CharReaderBuilder rbuilder;
     335          24 :         Json::Value preference;
     336          24 :         rbuilder["collectComments"] = false;
     337          24 :         std::string errs;
     338          24 :         if (file) {
     339          24 :             bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
     340          24 :             if (ok && root.isArray()) {
     341             :                 // Return if preference already exists
     342          72 :                 for (const auto& child : root)
     343          51 :                     if (child.get("key", "None").asString() == handlerName + "Always")
     344           3 :                         return;
     345             :             }
     346             :         }
     347             :         // Create preference structure otherwise
     348          21 :         preference["key"] = handlerName + "Always";
     349          21 :         preference["type"] = "Switch";
     350          21 :         preference["defaultValue"] = "0";
     351          21 :         preference["title"] = "Automatically turn " + handlerName + " on";
     352          21 :         preference["summary"] = handlerName + " will take effect immediately";
     353          21 :         preference["scope"] = "accountId";
     354          21 :         root.append(preference);
     355          36 :     }
     356          21 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     357          21 :     std::ofstream outFile(filePath);
     358          21 :     if (outFile) {
     359             :         // Save preference.json file with new "always preference"
     360          21 :         outFile << root.toStyledString();
     361          21 :         outFile.close();
     362             :     }
     363          27 : }
     364             : 
     365             : bool
     366          20 : PluginPreferencesUtils::getAlwaysPreference(const std::string& rootPath,
     367             :                                             const std::string& handlerName,
     368             :                                             const std::string& accountId)
     369             : {
     370          40 :     auto preferences = getPreferences(rootPath);
     371          20 :     auto accPrefs = getPreferences(rootPath, accountId);
     372         100 :     for (const auto& item : accPrefs) {
     373          80 :         preferences.push_back(item);
     374             :     }
     375          20 :     auto preferencesValues = getPreferencesValuesMap(rootPath, accountId);
     376             : 
     377         120 :     for (const auto& preference : preferences) {
     378         200 :         auto key = preference.at("key");
     379         360 :         if (preference.at("type") == "Switch" && key == handlerName + "Always"
     380         360 :             && preferencesValues.find(key)->second == "1")
     381           0 :             return true;
     382         100 :     }
     383             : 
     384          20 :     return false;
     385          20 : }
     386             : } // namespace jami

Generated by: LCOV version 1.14