LCOV - code coverage report
Current view: top level - src/plugin - pluginpreferencesutils.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 3.6 % 220 8
Test Date: 2026-06-13 09:18:46 Functions: 8.7 % 23 2

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

Generated by: LCOV version 2.0-1