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