LCOV - code coverage report
Current view: top level - foo/src/plugin - pluginpreferencesutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 8 222 3.6 %
Date: 2025-12-18 10:07:43 Functions: 2 13 15.4 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 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           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 category = jsonPreference.get("category", "NoCategory").asString();
     121           0 :                 std::string type = jsonPreference.get("type", "None").asString();
     122           0 :                 std::string key = jsonPreference.get("key", "None").asString();
     123             :                 // The preference must have at least type and key
     124           0 :                 if (type != "None" && key != "None") {
     125           0 :                     if (keys.find(key) == keys.end()) {
     126             :                         // Read the rest of the preference
     127           0 :                         auto preferenceAttributes = parsePreferenceConfig(jsonPreference);
     128             :                         // If the parsing of the attributes was successful, commit the map and the keys
     129           0 :                         auto defaultValue = preferenceAttributes.find("defaultValue");
     130           0 :                         if (type == "Path" && defaultValue != preferenceAttributes.end()) {
     131             :                             // defaultValue in a Path preference is an incomplete path
     132             :                             // starting from the installation path of the plugin.
     133             :                             // Here we complete the path value.
     134           0 :                             defaultValue->second = (rootPath / defaultValue->second).string();
     135             :                         }
     136             : 
     137           0 :                         if (!preferenceAttributes.empty()) {
     138           0 :                             for (const auto& locale : locales) {
     139           0 :                                 for (auto& pair : preferenceAttributes) {
     140           0 :                                     string_replace(pair.second, "{{" + locale.first + "}}", locale.second);
     141             :                                 }
     142             :                             }
     143           0 :                             preferences.push_back(std::move(preferenceAttributes));
     144           0 :                             keys.insert(key);
     145             :                         }
     146           0 :                     }
     147             :                 }
     148           0 :             }
     149             :         } else {
     150           0 :             JAMI_ERR() << "PluginPreferencesParser:: Failed to parse preferences.json for plugin: "
     151           0 :                        << preferenceFilePath;
     152             :         }
     153           0 :     }
     154             : 
     155           0 :     return preferences;
     156           0 : }
     157             : 
     158             : std::map<std::string, std::string>
     159           0 : PluginPreferencesUtils::getUserPreferencesValuesMap(const std::filesystem::path& rootPath, const std::string& accountId)
     160             : {
     161           0 :     auto preferencesValuesFilePath = valuesFilePath(rootPath, accountId);
     162           0 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
     163           0 :     std::ifstream file(preferencesValuesFilePath, std::ios::binary);
     164           0 :     std::map<std::string, std::string> rmap;
     165             : 
     166             :     // If file is accessible
     167           0 :     if (file.good()) {
     168             :         // Get file size
     169           0 :         std::string str;
     170           0 :         file.seekg(0, std::ios::end);
     171           0 :         size_t fileSize = static_cast<size_t>(file.tellg());
     172             :         // If not empty
     173           0 :         if (fileSize > 0) {
     174             :             // Read whole file content and put it in the string str
     175           0 :             str.reserve(static_cast<size_t>(file.tellg()));
     176           0 :             file.seekg(0, std::ios::beg);
     177           0 :             str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
     178           0 :             file.close();
     179             :             try {
     180             :                 // Unpack the string
     181           0 :                 msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
     182             :                 // Deserialized object is valid during the msgpack::object_handle instance is alive.
     183           0 :                 msgpack::object deserialized = oh.get();
     184           0 :                 deserialized.convert(rmap);
     185           0 :             } catch (const std::exception& e) {
     186           0 :                 JAMI_ERR() << e.what();
     187           0 :             }
     188             :         }
     189           0 :     }
     190           0 :     return rmap;
     191           0 : }
     192             : 
     193             : std::map<std::string, std::string>
     194           0 : PluginPreferencesUtils::getPreferencesValuesMap(const std::filesystem::path& rootPath, const std::string& accountId)
     195             : {
     196           0 :     std::map<std::string, std::string> rmap;
     197             : 
     198             :     // Read all preferences values
     199           0 :     std::vector<std::map<std::string, std::string>> preferences = getPreferences(rootPath);
     200           0 :     auto accPrefs = getPreferences(rootPath, accountId);
     201           0 :     for (const auto& item : accPrefs) {
     202           0 :         preferences.push_back(item);
     203             :     }
     204           0 :     for (auto& preference : preferences) {
     205           0 :         rmap[preference["key"]] = preference["defaultValue"];
     206             :     }
     207             : 
     208             :     // If any of these preferences were modified, its value is changed before return
     209           0 :     for (const auto& pair : getUserPreferencesValuesMap(rootPath)) {
     210           0 :         rmap[pair.first] = pair.second;
     211           0 :     }
     212             : 
     213           0 :     if (!accountId.empty()) {
     214             :         // If any of these preferences were modified, its value is changed before return
     215           0 :         for (const auto& pair : getUserPreferencesValuesMap(rootPath, accountId)) {
     216           0 :             rmap[pair.first] = pair.second;
     217           0 :         }
     218             :     }
     219             : 
     220           0 :     return rmap;
     221           0 : }
     222             : 
     223             : bool
     224           0 : PluginPreferencesUtils::resetPreferencesValuesMap(const std::string& rootPath, const std::string& accountId)
     225             : {
     226           0 :     bool returnValue = true;
     227           0 :     std::map<std::string, std::string> pluginPreferencesMap {};
     228             : 
     229           0 :     auto preferencesValuesFilePath = valuesFilePath(rootPath, accountId);
     230           0 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(preferencesValuesFilePath));
     231           0 :     std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
     232           0 :     if (!fs.good()) {
     233           0 :         return false;
     234             :     }
     235             :     try {
     236           0 :         msgpack::pack(fs, pluginPreferencesMap);
     237           0 :     } catch (const std::exception& e) {
     238           0 :         returnValue = false;
     239           0 :         JAMI_ERR() << e.what();
     240           0 :     }
     241             : 
     242           0 :     return returnValue;
     243           0 : }
     244             : 
     245             : void
     246           0 : PluginPreferencesUtils::setAllowDenyListPreferences(const ChatHandlerList& list)
     247             : {
     248           0 :     auto filePath = getAllowDenyListsPath();
     249           0 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     250           0 :     std::ofstream fs(filePath, std::ios::binary);
     251           0 :     if (!fs.good()) {
     252           0 :         return;
     253             :     }
     254             :     try {
     255           0 :         msgpack::pack(fs, list);
     256           0 :     } catch (const std::exception& e) {
     257           0 :         JAMI_ERR() << e.what();
     258           0 :     }
     259           0 : }
     260             : 
     261             : void
     262          32 : PluginPreferencesUtils::getAllowDenyListPreferences(ChatHandlerList& list)
     263             : {
     264          32 :     auto filePath = getAllowDenyListsPath();
     265          32 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     266          32 :     std::ifstream file(filePath, std::ios::binary);
     267             : 
     268             :     // If file is accessible
     269          32 :     if (file.good()) {
     270             :         // Get file size
     271           0 :         std::string str;
     272           0 :         file.seekg(0, std::ios::end);
     273           0 :         size_t fileSize = static_cast<size_t>(file.tellg());
     274             :         // If not empty
     275           0 :         if (fileSize > 0) {
     276             :             // Read whole file content and put it in the string str
     277           0 :             str.reserve(static_cast<size_t>(file.tellg()));
     278           0 :             file.seekg(0, std::ios::beg);
     279           0 :             str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
     280           0 :             file.close();
     281             :             try {
     282             :                 // Unpack the string
     283           0 :                 msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
     284             :                 // Deserialized object is valid during the msgpack::object_handle instance is alive.
     285           0 :                 msgpack::object deserialized = oh.get();
     286           0 :                 deserialized.convert(list);
     287           0 :             } catch (const std::exception& e) {
     288           0 :                 JAMI_ERR() << e.what();
     289           0 :             }
     290             :         }
     291           0 :     }
     292          32 : }
     293             : 
     294             : void
     295           0 : PluginPreferencesUtils::addAlwaysHandlerPreference(const std::string& handlerName, const std::string& rootPath)
     296             : {
     297             :     {
     298           0 :         auto filePath = getPreferencesConfigFilePath(rootPath);
     299           0 :         Json::Value root;
     300             : 
     301           0 :         std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     302           0 :         std::ifstream file(filePath);
     303           0 :         Json::CharReaderBuilder rbuilder;
     304           0 :         Json::Value preference;
     305           0 :         rbuilder["collectComments"] = false;
     306           0 :         std::string errs;
     307           0 :         if (file) {
     308           0 :             bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
     309           0 :             if (ok && root.isArray()) {
     310             :                 // Return if preference already exists
     311           0 :                 for (const auto& child : root)
     312           0 :                     if (child.get("key", "None").asString() == handlerName + "Always")
     313           0 :                         return;
     314             :             }
     315             :         }
     316           0 :     }
     317             : 
     318           0 :     auto filePath = getPreferencesConfigFilePath(rootPath, "acc");
     319           0 :     Json::Value root;
     320             :     {
     321           0 :         std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     322           0 :         std::ifstream file(filePath);
     323           0 :         Json::CharReaderBuilder rbuilder;
     324           0 :         Json::Value preference;
     325           0 :         rbuilder["collectComments"] = false;
     326           0 :         std::string errs;
     327           0 :         if (file) {
     328           0 :             bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
     329           0 :             if (ok && root.isArray()) {
     330             :                 // Return if preference already exists
     331           0 :                 for (const auto& child : root)
     332           0 :                     if (child.get("key", "None").asString() == handlerName + "Always")
     333           0 :                         return;
     334             :             }
     335             :         }
     336             :         // Create preference structure otherwise
     337           0 :         preference["key"] = handlerName + "Always";
     338           0 :         preference["type"] = "Switch";
     339           0 :         preference["defaultValue"] = "0";
     340           0 :         preference["title"] = "Automatically turn " + handlerName + " on";
     341           0 :         preference["summary"] = handlerName + " will take effect immediately";
     342           0 :         preference["scope"] = "accountId";
     343           0 :         root.append(preference);
     344           0 :     }
     345           0 :     std::lock_guard guard(dhtnet::fileutils::getFileLock(filePath));
     346           0 :     std::ofstream outFile(filePath);
     347           0 :     if (outFile) {
     348             :         // Save preference.json file with new "always preference"
     349           0 :         outFile << root.toStyledString();
     350           0 :         outFile.close();
     351             :     }
     352           0 : }
     353             : 
     354             : bool
     355           0 : PluginPreferencesUtils::getAlwaysPreference(const std::string& rootPath,
     356             :                                             const std::string& handlerName,
     357             :                                             const std::string& accountId)
     358             : {
     359           0 :     auto preferences = getPreferences(rootPath);
     360           0 :     auto accPrefs = getPreferences(rootPath, accountId);
     361           0 :     for (const auto& item : accPrefs) {
     362           0 :         preferences.push_back(item);
     363             :     }
     364           0 :     auto preferencesValues = getPreferencesValuesMap(rootPath, accountId);
     365             : 
     366           0 :     for (const auto& preference : preferences) {
     367           0 :         auto key = preference.at("key");
     368           0 :         if (preference.at("type") == "Switch" && key == handlerName + "Always"
     369           0 :             && preferencesValues.find(key)->second == "1")
     370           0 :             return true;
     371           0 :     }
     372             : 
     373           0 :     return false;
     374           0 : }
     375             : } // namespace jami

Generated by: LCOV version 1.14