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