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-12-21 08:56:24 Functions: 12 13 92.3 %

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

Generated by: LCOV version 1.14