jamipluginmanager.cpp 13.6 KB
Newer Older
Aline Gondim Santos's avatar
Aline Gondim Santos committed
1
/*
2
 *  Copyright (C) 2020-2022 Savoir-faire Linux Inc.
3
4
 *
 *  Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 *  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 "jamipluginmanager.h"
22
23
24
25
#include "pluginsutils.h"
#include "fileutils.h"
#include "archiver.h"

26
27
#include "logger.h"

28
#include <fstream>
29
#include <stdexcept>
30
#include <msgpack.hpp>
31
#include "manager.h"
32
#include "preferences.h"
33
#include "jami/plugin_manager_interface.h"
34

Sébastien Blin's avatar
Sébastien Blin committed
35
36
#define PLUGIN_ALREADY_INSTALLED 100 /* Plugin already installed with the same version */
#define PLUGIN_OLD_VERSION       200 /* Plugin already installed with a newer version */
37

38
#ifdef WIN32
39
#define LIB_TYPE   ".dll"
40
41
#define LIB_PREFIX ""
#else
Aline Gondim Santos's avatar
Aline Gondim Santos committed
42
#ifdef __APPLE__
43
#define LIB_TYPE   ".dylib"
Aline Gondim Santos's avatar
Aline Gondim Santos committed
44
45
#define LIB_PREFIX "lib"
#else
46
#define LIB_TYPE   ".so"
47
48
#define LIB_PREFIX "lib"
#endif
Aline Gondim Santos's avatar
Aline Gondim Santos committed
49
#endif
50

51
52
namespace jami {

Sébastien Blin's avatar
Sébastien Blin committed
53
54
std::map<std::string, std::string>
JamiPluginManager::getPluginDetails(const std::string& rootPath)
55
56
57
58
59
60
{
    auto detailsIt = pluginDetailsMap_.find(rootPath);
    if (detailsIt != pluginDetailsMap_.end()) {
        return detailsIt->second;
    }

61
62
    std::map<std::string, std::string> details = PluginUtils::parseManifestFile(
        PluginUtils::manifestPath(rootPath));
Sébastien Blin's avatar
Sébastien Blin committed
63
    if (!details.empty()) {
64
65
        auto it = details.find("iconPath");
        it->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
66
        details["soPath"] = rootPath + DIR_SEPARATOR_CH + LIB_PREFIX + details["name"] + LIB_TYPE;
67
        detailsIt = pluginDetailsMap_.emplace(rootPath, std::move(details)).first;
68
69
70
        return detailsIt->second;
    }
    return {};
71
72
}

Sébastien Blin's avatar
Sébastien Blin committed
73
std::vector<std::string>
74
JamiPluginManager::getInstalledPlugins()
75
{
76
    // Gets all plugins in standard path
77
78
    std::string pluginsPath = fileutils::get_data_dir() + DIR_SEPARATOR_CH + "plugins";
    std::vector<std::string> pluginsPaths = fileutils::readDirectory(pluginsPath);
Sébastien Blin's avatar
Sébastien Blin committed
79
80
81
    std::for_each(pluginsPaths.begin(), pluginsPaths.end(), [&pluginsPath](std::string& x) {
        x = pluginsPath + DIR_SEPARATOR_CH + x;
    });
82
83
84
85
86
    auto returnIterator = std::remove_if(pluginsPaths.begin(),
                                         pluginsPaths.end(),
                                         [](const std::string& path) {
                                             return !PluginUtils::checkPluginValidity(path);
                                         });
Sébastien Blin's avatar
Sébastien Blin committed
87
    pluginsPaths.erase(returnIterator, std::end(pluginsPaths));
88

89
    // Gets plugins installed in non standard path
90
91
92
    std::vector<std::string> nonStandardInstalls = jami::Manager::instance()
                                                       .pluginPreferences.getInstalledPlugins();
    for (auto& path : nonStandardInstalls) {
93
        if (PluginUtils::checkPluginValidity(path))
94
95
96
            pluginsPaths.emplace_back(path);
    }

97
98
99
    return pluginsPaths;
}

Sébastien Blin's avatar
Sébastien Blin committed
100
101
int
JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
102
{
Sébastien Blin's avatar
Sébastien Blin committed
103
104
105
    int r {0};
    if (fileutils::isFile(jplPath)) {
        try {
106
            auto manifestMap = PluginUtils::readPluginManifestFromArchive(jplPath);
Adrien Béraud's avatar
Adrien Béraud committed
107
            const std::string& name = manifestMap["name"];
108
109
            if (name.empty())
                return 0;
Adrien Béraud's avatar
Adrien Béraud committed
110
            const std::string& version = manifestMap["version"];
111
112
            std::string destinationDir {fileutils::get_data_dir() + DIR_SEPARATOR_CH + "plugins"
                                        + DIR_SEPARATOR_CH + name};
113
            // Find if there is an existing version of this plugin
114
115
            const auto alreadyInstalledManifestMap = PluginUtils::parseManifestFile(
                PluginUtils::manifestPath(destinationDir));
116
117
118
119

            if (!alreadyInstalledManifestMap.empty()) {
                if (force) {
                    r = uninstallPlugin(destinationDir);
Sébastien Blin's avatar
Sébastien Blin committed
120
                    if (r == 0) {
121
122
123
                        archiver::uncompressArchive(jplPath,
                                                    destinationDir,
                                                    PluginUtils::uncompressJplFunction);
124
125
126
127
128
                    }
                } else {
                    std::string installedVersion = alreadyInstalledManifestMap.at("version");
                    if (version > installedVersion) {
                        r = uninstallPlugin(destinationDir);
Sébastien Blin's avatar
Sébastien Blin committed
129
130
131
                        if (r == 0) {
                            archiver::uncompressArchive(jplPath,
                                                        destinationDir,
132
                                                        PluginUtils::uncompressJplFunction);
133
                        }
Sébastien Blin's avatar
Sébastien Blin committed
134
                    } else if (version == installedVersion) {
135
136
137
138
139
140
                        r = PLUGIN_ALREADY_INSTALLED;
                    } else {
                        r = PLUGIN_OLD_VERSION;
                    }
                }
            } else {
141
142
143
                archiver::uncompressArchive(jplPath,
                                            destinationDir,
                                            PluginUtils::uncompressJplFunction);
144
            }
Olivier Dion's avatar
Olivier Dion committed
145
            libjami::loadPlugin(destinationDir);
Sébastien Blin's avatar
Sébastien Blin committed
146
        } catch (const std::exception& e) {
147
148
149
150
151
152
            JAMI_ERR() << e.what();
        }
    }
    return r;
}

Sébastien Blin's avatar
Sébastien Blin committed
153
154
int
JamiPluginManager::uninstallPlugin(const std::string& rootPath)
155
{
156
    if (PluginUtils::checkPluginValidity(rootPath)) {
157
        auto detailsIt = pluginDetailsMap_.find(rootPath);
Sébastien Blin's avatar
Sébastien Blin committed
158
        if (detailsIt != pluginDetailsMap_.end()) {
159
            bool loaded = pm_.checkLoadedPlugin(rootPath);
Sébastien Blin's avatar
Sébastien Blin committed
160
            if (loaded) {
161
                JAMI_INFO() << "PLUGIN: unloading before uninstall.";
Olivier Dion's avatar
Olivier Dion committed
162
                bool status = libjami::unloadPlugin(rootPath);
Sébastien Blin's avatar
Sébastien Blin committed
163
                if (!status) {
164
165
166
167
                    JAMI_INFO() << "PLUGIN: could not unload, not performing uninstall.";
                    return -1;
                }
            }
168
169
170
171
            for (const auto& accId : jami::Manager::instance().getAccountList())
                fileutils::removeAll(fileutils::get_data_dir() + DIR_SEPARATOR_CH + accId
                                     + DIR_SEPARATOR_CH + "plugins" + DIR_SEPARATOR_CH
                                     + detailsIt->second.at("name"));
172
173
            pluginDetailsMap_.erase(detailsIt);
        }
174
175
        return fileutils::removeAll(rootPath);
    } else {
176
        JAMI_INFO() << "PLUGIN: not installed.";
177
178
179
180
        return -1;
    }
}

Sébastien Blin's avatar
Sébastien Blin committed
181
182
bool
JamiPluginManager::loadPlugin(const std::string& rootPath)
183
{
184
#ifdef ENABLE_PLUGIN
Sébastien Blin's avatar
Sébastien Blin committed
185
    try {
186
        bool status = pm_.load(getPluginDetails(rootPath).at("soPath"));
187
        JAMI_INFO() << "PLUGIN: load status - " << status;
188
189
190

        return status;

Sébastien Blin's avatar
Sébastien Blin committed
191
    } catch (const std::exception& e) {
192
193
194
        JAMI_ERR() << e.what();
        return false;
    }
195
196
#endif
    return false;
197
198
}

Sébastien Blin's avatar
Sébastien Blin committed
199
200
bool
JamiPluginManager::unloadPlugin(const std::string& rootPath)
201
{
202
#ifdef ENABLE_PLUGIN
Sébastien Blin's avatar
Sébastien Blin committed
203
    try {
204
        bool status = pm_.unload(getPluginDetails(rootPath).at("soPath"));
205
        JAMI_INFO() << "PLUGIN: unload status - " << status;
206
207

        return status;
Sébastien Blin's avatar
Sébastien Blin committed
208
    } catch (const std::exception& e) {
209
210
211
        JAMI_ERR() << e.what();
        return false;
    }
212
213
#endif
    return false;
214
215
}

Sébastien Blin's avatar
Sébastien Blin committed
216
std::vector<std::string>
217
JamiPluginManager::getLoadedPlugins() const
218
{
219
    std::vector<std::string> loadedSoPlugins = pm_.getLoadedPlugins();
Sébastien Blin's avatar
Sébastien Blin committed
220
    std::vector<std::string> loadedPlugins {};
221
    loadedPlugins.reserve(loadedSoPlugins.size());
Sébastien Blin's avatar
Sébastien Blin committed
222
223
224
    std::transform(loadedSoPlugins.begin(),
                   loadedSoPlugins.end(),
                   std::back_inserter(loadedPlugins),
Adrien Béraud's avatar
Adrien Béraud committed
225
                   [](const std::string& soPath) {
226
227
                       return PluginUtils::getRootPathFromSoPath(soPath);
                   });
228
229
230
    return loadedPlugins;
}

Sébastien Blin's avatar
Sébastien Blin committed
231
std::vector<std::map<std::string, std::string>>
232
JamiPluginManager::getPluginPreferences(const std::string& rootPath, const std::string& accountId)
233
{
234
    return PluginPreferencesUtils::getPreferences(rootPath, accountId);
235
236
}

Sébastien Blin's avatar
Sébastien Blin committed
237
238
bool
JamiPluginManager::setPluginPreference(const std::string& rootPath,
239
                                       const std::string& accountId,
Sébastien Blin's avatar
Sébastien Blin committed
240
241
                                       const std::string& key,
                                       const std::string& value)
242
{
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    std::string acc = accountId;

    // If we try to change a preference value linked to an account
    // but that preference is global, we must ignore accountId and
    // change the preference for every account
    if (!accountId.empty()) {
        // Get global preferences
        auto preferences = PluginPreferencesUtils::getPreferences(rootPath, "");
        // Check if the preference we want to change is global
        auto it = std::find_if(preferences.cbegin(),
                               preferences.cend(),
                               [key](const std::map<std::string, std::string>& preference) {
                                   return preference.at("key") == key;
                               });
        // Ignore accountId if global preference
        if (it != preferences.cend())
            acc.clear();
    }

262
    std::map<std::string, std::string> pluginUserPreferencesMap
263
        = PluginPreferencesUtils::getUserPreferencesValuesMap(rootPath, acc);
264
    std::map<std::string, std::string> pluginPreferencesMap
265
        = PluginPreferencesUtils::getPreferencesValuesMap(rootPath, acc);
266

267
    // If any plugin handler is active we may have to reload it
268
269
    bool force {pm_.checkLoadedPlugin(rootPath)};

270
    // We check if the preference is modified without having to reload plugin
271
272
273
274
    force &= preferencesm_.setPreference(key, value, rootPath, acc);
    force &= callsm_.setPreference(key, value, rootPath);
    force &= chatsm_.setPreference(key, value, rootPath);

275
276
    if (force)
        unloadPlugin(rootPath);
277

278
    // Save preferences.msgpack with modified preferences values
279
280
    auto find = pluginPreferencesMap.find(key);
    if (find != pluginPreferencesMap.end()) {
281
        pluginUserPreferencesMap[key] = value;
282
283
        const std::string preferencesValuesFilePath
            = PluginPreferencesUtils::valuesFilePath(rootPath, acc);
284
        std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
285
        std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
Sébastien Blin's avatar
Sébastien Blin committed
286
        if (!fs.good()) {
287
288
289
            if (force) {
                loadPlugin(rootPath);
            }
290
291
292
            return false;
        }
        try {
293
            msgpack::pack(fs, pluginUserPreferencesMap);
294
295
        } catch (const std::exception& e) {
            JAMI_ERR() << e.what();
296
297
298
            if (force) {
                loadPlugin(rootPath);
            }
Aline Gondim Santos's avatar
Aline Gondim Santos committed
299
            return false;
300
301
        }
    }
302
303
304
305
    if (force) {
        loadPlugin(rootPath);
    }
    return true;
306
307
}

Sébastien Blin's avatar
Sébastien Blin committed
308
std::map<std::string, std::string>
309
310
JamiPluginManager::getPluginPreferencesValuesMap(const std::string& rootPath,
                                                 const std::string& accountId)
311
{
312
    return PluginPreferencesUtils::getPreferencesValuesMap(rootPath, accountId);
313
314
}

Sébastien Blin's avatar
Sébastien Blin committed
315
bool
316
317
JamiPluginManager::resetPluginPreferencesValuesMap(const std::string& rootPath,
                                                   const std::string& accountId)
318
{
319
    bool acc {accountId.empty()};
320
    bool loaded {pm_.checkLoadedPlugin(rootPath)};
321
    if (loaded && acc)
322
        unloadPlugin(rootPath);
323
324
325
    auto status = PluginPreferencesUtils::resetPreferencesValuesMap(rootPath, accountId);
    preferencesm_.resetPreferences(rootPath, accountId);
    if (loaded && acc) {
326
327
328
        loadPlugin(rootPath);
    }
    return status;
329
330
}

Sébastien Blin's avatar
Sébastien Blin committed
331
332
void
JamiPluginManager::registerServices()
333
{
334
    // Register getPluginPreferences so that plugin's can receive it's preferences
Adrien Béraud's avatar
Adrien Béraud committed
335
    pm_.registerService("getPluginPreferences", [](const DLPlugin* plugin, void* data) {
336
        auto ppp = static_cast<std::map<std::string, std::string>*>(data);
337
338
        *ppp = PluginPreferencesUtils::getPreferencesValuesMap(
            PluginUtils::getRootPathFromSoPath(plugin->getPath()));
339
340
341
        return 0;
    });

342
    // Register getPluginDataPath so that plugin's can receive the path to it's data folder
Adrien Béraud's avatar
Adrien Béraud committed
343
344
345
    pm_.registerService("getPluginDataPath", [](const DLPlugin* plugin, void* data) {
        auto dataPath = static_cast<std::string*>(data);
        dataPath->assign(PluginUtils::dataPath(plugin->getPath()));
346
347
        return 0;
    });
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365

    // getPluginAccPreferences is a service that allows plugins to load saved per account preferences.
    auto getPluginAccPreferences = [](const DLPlugin* plugin, void* data) {
        const auto path = PluginUtils::getRootPathFromSoPath(plugin->getPath());
        auto preferencesPtr {(static_cast<PreferencesMap*>(data))};
        if (!preferencesPtr)
            return -1;

        preferencesPtr->emplace("default",
                                PluginPreferencesUtils::getPreferencesValuesMap(path, "default"));

        for (const auto& accId : jami::Manager::instance().getAccountList())
            preferencesPtr->emplace(accId,
                                    PluginPreferencesUtils::getPreferencesValuesMap(path, accId));
        return 0;
    };

    pm_.registerService("getPluginAccPreferences", getPluginAccPreferences);
366
}
Sébastien Blin's avatar
Sébastien Blin committed
367
} // namespace jami