-
To apply translations, first try to read if the JAMI_LANG variable is set, if not, try to get the system language. GitLab: #747 Change-Id: Ie458abcc07c0d0fd151172e172fe1418e5f06e7f
To apply translations, first try to read if the JAMI_LANG variable is set, if not, try to get the system language. GitLab: #747 Change-Id: Ie458abcc07c0d0fd151172e172fe1418e5f06e7f
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
pluginpreferencesutils.cpp 17.19 KiB
/*
* Copyright (C) 2020-2022 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA.
*/
#include "pluginpreferencesutils.h"
#include <msgpack.hpp>
#include <sstream>
#include <fstream>
#include <fmt/core.h>
#include "logger.h"
#include "fileutils.h"
namespace jami {
std::string
PluginPreferencesUtils::getPreferencesConfigFilePath(const std::string& rootPath,
const std::string& accountId)
{
if (accountId.empty())
return rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + "preferences.json";
else
return rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + "accountpreferences.json";
}
std::string
PluginPreferencesUtils::valuesFilePath(const std::string& rootPath, const std::string& accountId)
{
if (accountId.empty() || accountId == "default")
return rootPath + DIR_SEPARATOR_CH + "preferences.msgpack";
auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
auto dir = fileutils::get_data_dir() + DIR_SEPARATOR_CH + accountId + DIR_SEPARATOR_CH
+ "plugins" + DIR_SEPARATOR_CH + pluginName;
fileutils::check_dir(dir.c_str());
return dir + DIR_SEPARATOR_CH + "preferences.msgpack";
}
std::string
PluginPreferencesUtils::getAllowDenyListsPath()
{
return fileutils::get_data_dir() + DIR_SEPARATOR_CH + "plugins" + DIR_SEPARATOR_CH
+ "allowdeny.msgpack";
}
std::map<std::string, std::string>
PluginPreferencesUtils::processLocaleFile(const std::string& preferenceLocaleFilePath)
{
if (!fileutils::isFile(preferenceLocaleFilePath)) {
return {};
}
std::ifstream file(preferenceLocaleFilePath);
Json::Value root;
Json::CharReaderBuilder rbuilder;
rbuilder["collectComments"] = false;
std::string errs;
std::map<std::string, std::string> locales {};
if (file) {
// Read the file to a json format
if (Json::parseFromStream(rbuilder, file, &root, &errs)) {
auto keys = root.getMemberNames();
for (const auto& key : keys) {
locales[key] = root.get(key, "").asString();
}
}
}
return locales;
}
std::map<std::string, std::string>
PluginPreferencesUtils::getLocales(const std::string& rootPath, const std::string& lang)
{
auto pluginName = rootPath.substr(rootPath.find_last_of(DIR_SEPARATOR_CH) + 1);
auto basePath = fmt::format("{}/data/locale/{}", rootPath, pluginName + "_");
std::map<std::string, std::string> locales = {};
// Get language translations
if (!lang.empty()) {
locales = processLocaleFile(basePath + lang + ".json");
}
// Get default english values if no translations were found
if (locales.empty()) {
locales = processLocaleFile(basePath + "en.json");
}
return locales;
}
std::string
PluginPreferencesUtils::convertArrayToString(const Json::Value& jsonArray)
{
std::string stringArray {};
if (jsonArray.size()) {
for (unsigned i = 0; i < jsonArray.size() - 1; i++) {
if (jsonArray[i].isString()) {
stringArray += jsonArray[i].asString() + ",";
} else if (jsonArray[i].isArray()) {
stringArray += convertArrayToString(jsonArray[i]) + ",";
}
}
unsigned lastIndex = jsonArray.size() - 1;
if (jsonArray[lastIndex].isString()) {
stringArray += jsonArray[lastIndex].asString();
}
}
return stringArray;
}
std::map<std::string, std::string>
PluginPreferencesUtils::parsePreferenceConfig(const Json::Value& jsonPreference)
{
std::map<std::string, std::string> preferenceMap;
const auto& members = jsonPreference.getMemberNames();
// Insert other fields
for (const auto& member : members) {
const Json::Value& value = jsonPreference[member];
if (value.isString()) {
preferenceMap.emplace(member, jsonPreference[member].asString());
} else if (value.isArray()) {
preferenceMap.emplace(member, convertArrayToString(jsonPreference[member]));
}
}
return preferenceMap;
}
std::vector<std::map<std::string, std::string>>
PluginPreferencesUtils::getPreferences(const std::string& rootPath, const std::string& accountId)
{
std::string preferenceFilePath = getPreferencesConfigFilePath(rootPath, accountId);
std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferenceFilePath));
std::ifstream file(preferenceFilePath);
Json::Value root;
Json::CharReaderBuilder rbuilder;
rbuilder["collectComments"] = false;
std::string errs;
std::set<std::string> keys;
std::vector<std::map<std::string, std::string>> preferences;
if (file) {
// Get preferences locale
std::string lang;
if (auto envLang = std::getenv("JAMI_LANG"))
lang = envLang;
else
JAMI_ERR() << "Error getting JAMI_LANG env, trying to get system language";
// If language preference is empty, try to get from the system.
if (lang.empty()) {
#ifdef WIN32
WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
char utf8Buffer[LOCALE_NAME_MAX_LENGTH] {};
WideCharToMultiByte(CP_UTF8,
0,
localeBuffer,
LOCALE_NAME_MAX_LENGTH,
utf8Buffer,
LOCALE_NAME_MAX_LENGTH,
nullptr,
nullptr);
lang.append(utf8Buffer);
string_replace(lang, "-", "_");
}
#else
// For Android this should not work since std::locale is not supported by the NDK.
lang = std::locale("").name();
#endif // WIN32
}
auto locales = getLocales(rootPath, std::string(string_remove_suffix(lang, '.')));
// Read the file to a json format
bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
if (ok && root.isArray()) {
// Read each preference described in preference.json individually
for (unsigned i = 0; i < root.size(); i++) {
const Json::Value& jsonPreference = root[i];
std::string category = jsonPreference.get("category", "NoCategory").asString();
std::string type = jsonPreference.get("type", "None").asString();
std::string key = jsonPreference.get("key", "None").asString();
// The preference must have at least type and key
if (type != "None" && key != "None") {
if (keys.find(key) == keys.end()) {
// Read the rest of the preference
auto preferenceAttributes = parsePreferenceConfig(jsonPreference);
// If the parsing of the attributes was successful, commit the map and the keys
auto defaultValue = preferenceAttributes.find("defaultValue");
if (type == "Path" && defaultValue != preferenceAttributes.end()) {
// defaultValue in a Path preference is an incomplete path
// starting from the installation path of the plugin.
// Here we complete the path value.
defaultValue->second = rootPath + DIR_SEPARATOR_STR
+ defaultValue->second;
}
if (!preferenceAttributes.empty()) {
for (const auto& locale : locales) {
for (auto& pair : preferenceAttributes) {
string_replace(pair.second,
"{{" + locale.first + "}}",
locale.second);
}
}
preferences.push_back(std::move(preferenceAttributes));
keys.insert(key);
}
}
}
}
} else {
JAMI_ERR() << "PluginPreferencesParser:: Failed to parse preferences.json for plugin: "
<< preferenceFilePath;
}
}
return preferences;
}
std::map<std::string, std::string>
PluginPreferencesUtils::getUserPreferencesValuesMap(const std::string& rootPath,
const std::string& accountId)
{
const std::string preferencesValuesFilePath = valuesFilePath(rootPath, accountId);
std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
std::ifstream file(preferencesValuesFilePath, std::ios::binary);
std::map<std::string, std::string> rmap;
// If file is accessible
if (file.good()) {
// Get file size
std::string str;
file.seekg(0, std::ios::end);
size_t fileSize = static_cast<size_t>(file.tellg());
// If not empty
if (fileSize > 0) {
// Read whole file content and put it in the string str
str.reserve(static_cast<size_t>(file.tellg()));
file.seekg(0, std::ios::beg);
str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
try {
// Unpack the string
msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
// Deserialized object is valid during the msgpack::object_handle instance is alive.
msgpack::object deserialized = oh.get();
deserialized.convert(rmap);
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
}
}
return rmap;
}
std::map<std::string, std::string>
PluginPreferencesUtils::getPreferencesValuesMap(const std::string& rootPath,
const std::string& accountId)
{
std::map<std::string, std::string> rmap;
// Read all preferences values
std::vector<std::map<std::string, std::string>> preferences = getPreferences(rootPath);
auto accPrefs = getPreferences(rootPath, accountId);
for (const auto& item : accPrefs) {
preferences.push_back(item);
}
for (auto& preference : preferences) {
rmap[preference["key"]] = preference["defaultValue"];
}
// If any of these preferences were modified, its value is changed before return
for (const auto& pair : getUserPreferencesValuesMap(rootPath)) {
rmap[pair.first] = pair.second;
}
if (!accountId.empty()) {
// If any of these preferences were modified, its value is changed before return
for (const auto& pair : getUserPreferencesValuesMap(rootPath, accountId)) {
rmap[pair.first] = pair.second;
}
}
return rmap;
}
bool
PluginPreferencesUtils::resetPreferencesValuesMap(const std::string& rootPath,
const std::string& accountId)
{
bool returnValue = true;
std::map<std::string, std::string> pluginPreferencesMap {};
const std::string preferencesValuesFilePath = valuesFilePath(rootPath, accountId);
std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
if (!fs.good()) {
return false;
}
try {
msgpack::pack(fs, pluginPreferencesMap);
} catch (const std::exception& e) {
returnValue = false;
JAMI_ERR() << e.what();
}
return returnValue;
}
void
PluginPreferencesUtils::setAllowDenyListPreferences(const ChatHandlerList& list)
{
std::string filePath = getAllowDenyListsPath();
std::lock_guard<std::mutex> guard(fileutils::getFileLock(filePath));
std::ofstream fs(filePath, std::ios::binary);
if (!fs.good()) {
return;
}
try {
msgpack::pack(fs, list);
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
}
void
PluginPreferencesUtils::getAllowDenyListPreferences(ChatHandlerList& list)
{
const std::string filePath = getAllowDenyListsPath();
std::lock_guard<std::mutex> guard(fileutils::getFileLock(filePath));
std::ifstream file(filePath, std::ios::binary);
// If file is accessible
if (file.good()) {
// Get file size
std::string str;
file.seekg(0, std::ios::end);
size_t fileSize = static_cast<size_t>(file.tellg());
// If not empty
if (fileSize > 0) {
// Read whole file content and put it in the string str
str.reserve(static_cast<size_t>(file.tellg()));
file.seekg(0, std::ios::beg);
str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
try {
// Unpack the string
msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
// Deserialized object is valid during the msgpack::object_handle instance is alive.
msgpack::object deserialized = oh.get();
deserialized.convert(list);
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
}
}
}
void
PluginPreferencesUtils::addAlwaysHandlerPreference(const std::string& handlerName,
const std::string& rootPath)
{
{
std::string filePath = getPreferencesConfigFilePath(rootPath);
Json::Value root;
std::lock_guard<std::mutex> guard(fileutils::getFileLock(filePath));
std::ifstream file(filePath);
Json::CharReaderBuilder rbuilder;
Json::Value preference;
rbuilder["collectComments"] = false;
std::string errs;
if (file) {
bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
if (ok && root.isArray()) {
// Return if preference already exists
for (const auto& child : root)
if (child.get("key", "None").asString() == handlerName + "Always")
return;
}
}
}
std::string filePath = getPreferencesConfigFilePath(rootPath, "acc");
Json::Value root;
{
std::lock_guard<std::mutex> guard(fileutils::getFileLock(filePath));
std::ifstream file(filePath);
Json::CharReaderBuilder rbuilder;
Json::Value preference;
rbuilder["collectComments"] = false;
std::string errs;
if (file) {
bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
if (ok && root.isArray()) {
// Return if preference already exists
for (const auto& child : root)
if (child.get("key", "None").asString() == handlerName + "Always")
return;
}
}
// Create preference structure otherwise
preference["key"] = handlerName + "Always";
preference["type"] = "Switch";
preference["defaultValue"] = "0";
preference["title"] = "Automatically turn " + handlerName + " on";
preference["summary"] = handlerName + " will take effect immediately";
preference["scope"] = "accountId";
root.append(preference);
}
std::lock_guard<std::mutex> guard(fileutils::getFileLock(filePath));
std::ofstream outFile(filePath);
if (outFile) {
// Save preference.json file with new "always preference"
outFile << root.toStyledString();
outFile.close();
}
}
bool
PluginPreferencesUtils::getAlwaysPreference(const std::string& rootPath,
const std::string& handlerName,
const std::string& accountId)
{
auto preferences = getPreferences(rootPath);
auto accPrefs = getPreferences(rootPath, accountId);
for (const auto& item : accPrefs) {
preferences.push_back(item);
}
auto preferencesValues = getPreferencesValuesMap(rootPath, accountId);
for (const auto& preference : preferences) {
auto key = preference.at("key");
if (preference.at("type") == "Switch" && key == handlerName + "Always"
&& preferencesValues.find(key)->second == "1")
return true;
}
return false;
}
} // namespace jami