mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-07 22:02:12 +08:00
plugins: add plugin manager
Change-Id: I2946387c8f30151a9ce2b979e5d395600bfa7dae
This commit is contained in:
@ -208,6 +208,7 @@ namespace std {
|
||||
%include "datatransfer.i"
|
||||
%include "presencemanager.i"
|
||||
%include "videomanager.i"
|
||||
%include "plugin_manager_interface.i"
|
||||
|
||||
#include "dring/callmanager_interface.h"
|
||||
|
||||
|
20
bin/jni/plugin_manager_interface.i
Normal file
20
bin/jni/plugin_manager_interface.i
Normal file
@ -0,0 +1,20 @@
|
||||
%header %{
|
||||
|
||||
#include "dring/dring.h"
|
||||
#include "dring/plugin_manager_interface.h"
|
||||
%}
|
||||
|
||||
namespace DRing {
|
||||
bool loadPlugin(const std::string& path);
|
||||
bool unloadPlugin(const std::string& path);
|
||||
void togglePlugin(const std::string& path, bool toggle);
|
||||
std::map<std::string,std::string> getPluginDetails(const std::string& path);
|
||||
std::vector<std::map<std::string,std::string>> getPluginPreferences(const std::string& path);
|
||||
bool setPluginPreference(const std::string& path, const std::string& key, const std::string& value);
|
||||
std::map<std::string,std::string> getPluginPreferencesValues(const std::string& path);
|
||||
bool resetPluginPreferencesValues(const std::string& path);
|
||||
std::vector<std::string> listAvailablePlugins();
|
||||
std::vector<std::string> listLoadedPlugins();
|
||||
int installPlugin(const std::string& jplPath, bool force);
|
||||
int uninstallPlugin(const std::string& pluginRootPath);
|
||||
}
|
@ -161,7 +161,15 @@ libring_la_SOURCES = \
|
||||
generic_io.h \
|
||||
scheduled_executor.h \
|
||||
scheduled_executor.cpp \
|
||||
transport/peer_channel.h
|
||||
transport/peer_channel.h \
|
||||
plugin/pluginloader.h \
|
||||
plugin/pluginloaderdl.cpp \
|
||||
plugin/pluginmanager.h \
|
||||
plugin/pluginmanager.cpp \
|
||||
plugin/mediahandler.h \
|
||||
plugin/callservicemanager.h \
|
||||
plugin/jamipluginmanager.h \
|
||||
plugin/jamipluginmanager.cpp
|
||||
|
||||
if HAVE_WIN32
|
||||
libring_la_SOURCES += \
|
||||
|
@ -17,6 +17,7 @@ libclient_la_SOURCES = \
|
||||
callmanager.cpp \
|
||||
configurationmanager.cpp \
|
||||
datatransfer.cpp \
|
||||
plugin_manager_interface.cpp \
|
||||
$(PRESENCE_SRC) \
|
||||
$(VIDEO_SRC)
|
||||
|
||||
|
83
src/client/plugin_manager_interface.cpp
Normal file
83
src/client/plugin_manager_interface.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 "plugin_manager_interface.h"
|
||||
#include "manager.h"
|
||||
#include "plugin/jamipluginmanager.h"
|
||||
#include "logger.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace DRing {
|
||||
//
|
||||
bool
|
||||
loadPlugin(const std::string& path){
|
||||
return jami::Manager::instance().getJamiPluginManager().loadPlugin(path);
|
||||
}
|
||||
|
||||
bool
|
||||
unloadPlugin(const std::string& path){
|
||||
return jami::Manager::instance().getJamiPluginManager().unloadPlugin(path);
|
||||
}
|
||||
|
||||
void
|
||||
togglePlugin(const std::string& path, bool toggle){
|
||||
jami::Manager::instance().getJamiPluginManager().togglePlugin(path,toggle);
|
||||
}
|
||||
|
||||
std::map<std::string,std::string>
|
||||
getPluginDetails(const std::string& path){
|
||||
return jami::Manager::instance().getJamiPluginManager().getPluginDetails(path);
|
||||
}
|
||||
|
||||
std::vector<std::map<std::string,std::string>>
|
||||
getPluginPreferences(const std::string& path){
|
||||
return jami::Manager::instance().getJamiPluginManager().getPluginPreferences(path);
|
||||
}
|
||||
|
||||
bool
|
||||
setPluginPreference(const std::string& path, const std::string& key, const std::string& value) {
|
||||
return jami::Manager::instance().getJamiPluginManager().setPluginPreference(path, key, value);
|
||||
}
|
||||
|
||||
std::map<std::string,std::string>
|
||||
getPluginPreferencesValues(const std::string& path){
|
||||
return jami::Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(path);
|
||||
}
|
||||
bool
|
||||
resetPluginPreferencesValues(const std::string& path){
|
||||
return jami::Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(path);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
listAvailablePlugins() {
|
||||
return jami::Manager::instance().getJamiPluginManager().listAvailablePlugins();
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
listLoadedPlugins() {
|
||||
return jami::Manager::instance().getJamiPluginManager().listLoadedPlugins();
|
||||
}
|
||||
|
||||
int installPlugin(const std::string& jplPath, bool force) {
|
||||
return jami::Manager::instance().getJamiPluginManager().installPlugin(jplPath, force);
|
||||
}
|
||||
|
||||
int uninstallPlugin(const std::string& pluginRootPath) {
|
||||
return jami::Manager::instance().getJamiPluginManager().uninstallPlugin(pluginRootPath);
|
||||
}
|
||||
}
|
41
src/dring/plugin_manager_interface.h
Normal file
41
src/dring/plugin_manager_interface.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "def.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace DRing {
|
||||
DRING_PUBLIC bool loadPlugin(const std::string& path);
|
||||
DRING_PUBLIC bool unloadPlugin(const std::string& path);
|
||||
DRING_PUBLIC void togglePlugin(const std::string& path, bool toggle);
|
||||
DRING_PUBLIC std::map<std::string,std::string> getPluginDetails(const std::string& path);
|
||||
DRING_PUBLIC std::vector<std::map<std::string,std::string>> getPluginPreferences(const std::string& path);
|
||||
DRING_PUBLIC bool setPluginPreference(const std::string& path, const std::string& key, const std::string& value);
|
||||
DRING_PUBLIC std::map<std::string,std::string> getPluginPreferencesValues(const std::string& path);
|
||||
DRING_PUBLIC bool resetPluginPreferencesValues(const std::string& path);
|
||||
DRING_PUBLIC std::vector<std::string> listAvailablePlugins();
|
||||
DRING_PUBLIC std::vector<std::string> listLoadedPlugins();
|
||||
DRING_PUBLIC int installPlugin(const std::string& jplPath, bool force);
|
||||
DRING_PUBLIC int uninstallPlugin(const std::string& pluginRootPath);
|
||||
}
|
||||
|
@ -46,12 +46,14 @@
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h> // mode_t
|
||||
#define DIR_SEPARATOR_STR "/" // Directory separator char
|
||||
#define DIR_SEPARATOR_CH '/' // Directory separator string
|
||||
#define DIR_SEPARATOR_STR "/" // Directory separator string
|
||||
#define DIR_SEPARATOR_CH '/' // Directory separator char
|
||||
#define DIR_SEPARATOR_STR_ESC "\\/" // Escaped directory separator string
|
||||
#else
|
||||
#define mode_t unsigned
|
||||
#define DIR_SEPARATOR_STR "\\" // Directory separator char
|
||||
#define DIR_SEPARATOR_CH '\\' // Directory separator string
|
||||
#define DIR_SEPARATOR_STR "\\" // Directory separator string
|
||||
#define DIR_SEPARATOR_CH '\\' // Directory separator char
|
||||
#define DIR_SEPARATOR_STR_ESC "\\\\" // Escaped directory separator string
|
||||
#endif
|
||||
|
||||
namespace jami { namespace fileutils {
|
||||
|
@ -63,6 +63,7 @@ using random_device = dht::crypto::random_device;
|
||||
#include "audio/sound/tonelist.h"
|
||||
#include "audio/sound/dtmf.h"
|
||||
#include "audio/ringbufferpool.h"
|
||||
#include "plugin/jamipluginmanager.h"
|
||||
|
||||
#ifdef ENABLE_VIDEO
|
||||
#include "client/videomanager.h"
|
||||
@ -415,6 +416,8 @@ struct Manager::ManagerPimpl
|
||||
#endif
|
||||
|
||||
std::unique_ptr<SIPVoIPLink> sipLink_;
|
||||
/* Jami Plugin Manager */
|
||||
JamiPluginManager jami_plugin_manager;
|
||||
};
|
||||
|
||||
Manager::ManagerPimpl::ManagerPimpl(Manager& base)
|
||||
@ -3099,6 +3102,11 @@ Manager::sipVoIPLink() const
|
||||
}
|
||||
|
||||
|
||||
JamiPluginManager& Manager::getJamiPluginManager() const
|
||||
{
|
||||
return pimpl_->jami_plugin_manager;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string>
|
||||
Manager::getNearbyPeers(const std::string& accountID)
|
||||
{
|
||||
|
@ -61,6 +61,7 @@ class IceTransportFactory;
|
||||
class DataTransferFacade;
|
||||
class JamiAccount;
|
||||
class SIPVoIPLink;
|
||||
class JamiPluginManager;
|
||||
|
||||
static constexpr uint64_t DRING_ID_MAX_VAL = 9007199254740992;
|
||||
|
||||
@ -904,6 +905,7 @@ class DRING_TESTABLE Manager {
|
||||
std::vector<DRing::Message> getLastMessages(const std::string& accountID, const uint64_t& base_timestamp);
|
||||
|
||||
SIPVoIPLink& sipVoIPLink() const;
|
||||
JamiPluginManager& getJamiPluginManager() const;
|
||||
|
||||
private:
|
||||
Manager();
|
||||
|
109
src/plugin/jamiplugin.h
Normal file
109
src/plugin/jamiplugin.h
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Guillaume Roguez <guillaume.roguez@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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define EXTERNAL_C_LINKAGE extern "C"
|
||||
#define C_INTERFACE_START EXTERNAL_C_LINKAGE {
|
||||
#define C_INTERFACE_END }
|
||||
#else
|
||||
#define C_LINKAGE
|
||||
#define C_INTERFACE_START
|
||||
#define C_INTERFACE_END
|
||||
#endif
|
||||
|
||||
#define JAMI_PLUGIN_ABI_VERSION 1 /* 0 doesn't exist, considered as error */
|
||||
#define JAMI_PLUGIN_API_VERSION 1 /* 0 doesn't exist, considered as error */
|
||||
|
||||
C_INTERFACE_START;
|
||||
|
||||
typedef struct JAMI_PluginVersion {
|
||||
/* plugin is not loadable if this number differs from one
|
||||
* stored in the plugin loader */
|
||||
uint32_t abi;
|
||||
|
||||
/* a difference on api number may be acceptable, see the loader code */
|
||||
uint32_t api;
|
||||
} JAMI_PluginVersion;
|
||||
|
||||
struct JAMI_PluginAPI;
|
||||
|
||||
/* JAMI_PluginCreateFunc parameters */
|
||||
typedef struct JAMI_PluginObjectParams {
|
||||
const JAMI_PluginAPI *pluginApi; /* this API */
|
||||
const char *type;
|
||||
} JAMI_PluginObjectParams;
|
||||
|
||||
typedef void *(*JAMI_PluginCreateFunc)(JAMI_PluginObjectParams *params,
|
||||
void *closure);
|
||||
|
||||
typedef void (*JAMI_PluginDestroyFunc)(void *object, void *closure);
|
||||
|
||||
/* JAMI_PluginAPI.registerObjectFactory data */
|
||||
typedef struct JAMI_PluginObjectFactory {
|
||||
JAMI_PluginVersion version;
|
||||
void *closure; /* closure for create */
|
||||
JAMI_PluginCreateFunc create;
|
||||
JAMI_PluginDestroyFunc destroy;
|
||||
} JAMI_PluginObjectFactory;
|
||||
|
||||
/* Plugins exposed API prototype */
|
||||
typedef int32_t (*JAMI_PluginFunc)(const JAMI_PluginAPI *api, const char *name,
|
||||
void *data);
|
||||
|
||||
/* JAMI_PluginInitFunc parameters.
|
||||
* This structure is filled by the Plugin manager.
|
||||
* For backware compatibility, never c
|
||||
*/
|
||||
typedef struct JAMI_PluginAPI {
|
||||
JAMI_PluginVersion version; /* structure version, always the first data */
|
||||
void *context; /* opaque structure used by next functions */
|
||||
|
||||
/* API usable by plugin implementors */
|
||||
JAMI_PluginFunc registerObjectFactory;
|
||||
JAMI_PluginFunc invokeService;
|
||||
JAMI_PluginFunc manageComponent;
|
||||
} JAMI_PluginAPI;
|
||||
|
||||
typedef void (*JAMI_PluginExitFunc)(void);
|
||||
|
||||
typedef JAMI_PluginExitFunc (*JAMI_PluginInitFunc)(const JAMI_PluginAPI *api);
|
||||
|
||||
C_INTERFACE_END;
|
||||
|
||||
#define JAMI_DYN_INIT_FUNC_NAME "JAMI_dynPluginInit"
|
||||
#define JAMI_PLUGIN_INIT_STATIC(fname, pname) JAMI_PLUGIN_INIT(fname, pname)
|
||||
#define JAMI_PLUGIN_INIT_DYNAMIC(pname) \
|
||||
JAMI_PLUGIN_INIT(JAMI_dynPluginInit, pname)
|
||||
|
||||
/* Define here platform dependent way to export a declaration x to the dynamic
|
||||
* loading system.
|
||||
*/
|
||||
|
||||
/* Default case (like POSIX/.so) */
|
||||
|
||||
#define JAMI_PLUGIN_INIT(fname, pname) \
|
||||
(EXTERNAL_C_LINKAGE JAMI_PluginExitFunc fname(const JAMI_PluginAPI *pname))
|
||||
#define JAMI_PLUGIN_EXIT(fname) (EXTERNAL_C_LINKAGE void fname(void))
|
||||
|
452
src/plugin/jamipluginmanager.cpp
Normal file
452
src/plugin/jamipluginmanager.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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"
|
||||
#include "logger.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
|
||||
//Manager
|
||||
#include "manager.h"
|
||||
|
||||
extern "C" {
|
||||
#include <archive.h>
|
||||
}
|
||||
|
||||
#include <json/json.h>
|
||||
#include <msgpack.hpp>
|
||||
|
||||
#if defined(__arm__)
|
||||
#if defined(__ARM_ARCH_7A__)
|
||||
#define ABI "armeabi-v7a"
|
||||
#else
|
||||
#define ABI "armeabi"
|
||||
#endif
|
||||
#elif defined(__i386__)
|
||||
#define ABI "x86"
|
||||
#elif defined(__x86_64__)
|
||||
#define ABI "x86_64"
|
||||
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
|
||||
#define ABI "mips64"
|
||||
#elif defined(__mips__)
|
||||
#define ABI "mips"
|
||||
#elif defined(__aarch64__)
|
||||
#define ABI "arm64-v8a"
|
||||
#else
|
||||
#define ABI "unknown"
|
||||
#endif
|
||||
|
||||
#define PLUGIN_ALREADY_INSTALLED 100 /* Plugin already installed with the same version */
|
||||
#define PLUGIN_OLD_VERSION 200 /* Plugin already installed with a newer version */
|
||||
|
||||
namespace jami {
|
||||
|
||||
std::map<std::string, std::string> checkManifestJsonContentValidity(const Json::Value& root) {
|
||||
std::string name = root.get("name", "").asString();
|
||||
std::string description = root.get("description", "").asString();
|
||||
std::string version = root.get("version", "").asString();
|
||||
if(!name.empty() || !version.empty()){
|
||||
return {
|
||||
{"name", name},
|
||||
{"description", description},
|
||||
{"version", version},
|
||||
};
|
||||
} else {
|
||||
throw std::runtime_error("plugin manifest file: bad format");
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> checkManifestValidity(std::istream& stream) {
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
rbuilder["collectComments"] = false;
|
||||
std::string errs;
|
||||
|
||||
bool ok = Json::parseFromStream(rbuilder, stream, &root, &errs);
|
||||
|
||||
if(ok) {
|
||||
return checkManifestJsonContentValidity(root);
|
||||
} else{
|
||||
throw std::runtime_error("failed to parse the plugin manifest file");
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> checkManifestValidity(const std::vector<uint8_t>& vec) {
|
||||
Json::Value root;
|
||||
std::unique_ptr<Json::CharReader> json_Reader(Json::CharReaderBuilder{}.newCharReader());
|
||||
std::string errs;
|
||||
|
||||
bool ok = json_Reader->parse(reinterpret_cast<const char *>(vec.data()),
|
||||
reinterpret_cast<const char *>(vec.data()+vec.size()),
|
||||
&root,&errs);
|
||||
|
||||
if(ok) {
|
||||
return checkManifestJsonContentValidity(root);
|
||||
} else{
|
||||
throw std::runtime_error("failed to parse the plugin manifest file");
|
||||
}
|
||||
}
|
||||
|
||||
static const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
|
||||
static const std::regex SO_REGEX("([a-z0-9]+(?:[_-]?[a-z0-9]+)*)" DIR_SEPARATOR_STR_ESC "([a-z0-9_]+\\.(so|dll))");
|
||||
|
||||
std::pair<bool,const std::string>
|
||||
uncompressJplFunction(const std::string& relativeFileName)
|
||||
{
|
||||
std::smatch match;
|
||||
if (relativeFileName == "manifest.json" || std::regex_match(relativeFileName, DATA_REGEX)){
|
||||
return std::make_pair(true, relativeFileName);
|
||||
} else if (regex_search(relativeFileName, match, SO_REGEX) == true) {
|
||||
if (match.str(1) == ABI) {
|
||||
return std::make_pair(true, match.str(2));
|
||||
}
|
||||
}
|
||||
return std::make_pair(false, std::string{""});
|
||||
}
|
||||
|
||||
std::string convertArrayToString(const Json::Value& jsonArray)
|
||||
{
|
||||
std::string stringArray = "[";
|
||||
|
||||
for(int i=0; i< static_cast<int>(jsonArray.size()) - 1; i++) {
|
||||
if(jsonArray[i].isString()) {
|
||||
stringArray+=jsonArray[i].asString()+",";
|
||||
} else if(jsonArray[i].isArray()) {
|
||||
stringArray+=convertArrayToString(jsonArray[i])+",";
|
||||
}
|
||||
}
|
||||
|
||||
int lastIndex = static_cast<int>(jsonArray.size()) - 1;
|
||||
if(jsonArray[lastIndex].isString()) {
|
||||
stringArray+=jsonArray[lastIndex].asString();
|
||||
}
|
||||
|
||||
stringArray+="]";
|
||||
|
||||
return stringArray;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> parsePreferenceConfig(const Json::Value &jsonPreference, const std::string &type)
|
||||
{
|
||||
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::map<std::string, std::string> JamiPluginManager::getPluginDetails(const std::string &rootPath)
|
||||
{
|
||||
auto detailsIt = pluginDetailsMap_.find(rootPath);
|
||||
if (detailsIt != pluginDetailsMap_.end()) {
|
||||
return detailsIt->second;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> details = parseManifestFile(manifestPath(rootPath));
|
||||
details["iconPath"] = rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + "icon.png";
|
||||
details["soPath"] = rootPath + DIR_SEPARATOR_CH + "lib" + details["name"] + ".so";
|
||||
detailsIt = pluginDetailsMap_.emplace(rootPath, std::move(details)).first;
|
||||
return detailsIt->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> JamiPluginManager::listAvailablePlugins()
|
||||
{
|
||||
std::string pluginsPath = fileutils::get_data_dir() + DIR_SEPARATOR_CH + "plugins";
|
||||
std::vector<std::string> pluginsPaths = fileutils::readDirectory(pluginsPath);
|
||||
std::for_each(pluginsPaths.begin(), pluginsPaths.end(),
|
||||
[&pluginsPath](std::string& x){ x = pluginsPath + DIR_SEPARATOR_CH + x;});
|
||||
auto predicate = [this](std::string path){ return !checkPluginValidity(path);};
|
||||
auto returnIterator = std::remove_if(pluginsPaths.begin(),pluginsPaths.end(),predicate);
|
||||
pluginsPaths.erase(returnIterator,std::end(pluginsPaths));
|
||||
return pluginsPaths;
|
||||
}
|
||||
|
||||
int JamiPluginManager::installPlugin(const std::string &jplPath, bool force)
|
||||
{
|
||||
int r{0};
|
||||
if(fileutils::isFile(jplPath)) {
|
||||
try{
|
||||
auto manifestMap = readPluginManifestFromArchive(jplPath);
|
||||
std::string name = manifestMap["name"];
|
||||
std::string version = manifestMap["version"];
|
||||
const std::string destinationDir{fileutils::get_data_dir()
|
||||
+ DIR_SEPARATOR_CH + "plugins"
|
||||
+ DIR_SEPARATOR_CH + name};
|
||||
// Find if there is an existing version of this plugin
|
||||
const auto alreadyInstalledManifestMap = parseManifestFile(manifestPath(destinationDir));
|
||||
|
||||
if (!alreadyInstalledManifestMap.empty()) {
|
||||
if (force) {
|
||||
r = uninstallPlugin(destinationDir);
|
||||
if(r == 0) {
|
||||
archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
|
||||
}
|
||||
} else {
|
||||
std::string installedVersion = alreadyInstalledManifestMap.at("version");
|
||||
if (version > installedVersion) {
|
||||
r = uninstallPlugin(destinationDir);
|
||||
if(r == 0) {
|
||||
archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
|
||||
}
|
||||
} else if (version == installedVersion){
|
||||
// An error code of 100 to know that this version is the same as the one installed
|
||||
r = PLUGIN_ALREADY_INSTALLED;
|
||||
} else {
|
||||
// An error code of 100 to know that this version is older than the one installed
|
||||
r = PLUGIN_OLD_VERSION;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int JamiPluginManager::uninstallPlugin(const std::string &rootPath)
|
||||
{
|
||||
if(checkPluginValidity(rootPath)) {
|
||||
return fileutils::removeAll(rootPath);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool JamiPluginManager::loadPlugin(const std::string &rootPath)
|
||||
{
|
||||
try {
|
||||
return pm_.load(getPluginDetails(rootPath).at("soPath"));
|
||||
} catch(const std::exception& e) {
|
||||
JAMI_ERR() << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool JamiPluginManager::unloadPlugin(const std::string &rootPath)
|
||||
{
|
||||
try {
|
||||
return pm_.unload(getPluginDetails(rootPath).at("soPath"));
|
||||
} catch(const std::exception& e) {
|
||||
JAMI_ERR() << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void JamiPluginManager::togglePlugin(const std::string &rootPath, bool toggle)
|
||||
{
|
||||
try {
|
||||
std::string soPath = getPluginDetails(rootPath).at("soPath");
|
||||
// remove the previous plugin object if it was registered
|
||||
pm_.destroyPluginComponents(soPath);
|
||||
// If toggle, register a new instance of the plugin
|
||||
// function
|
||||
if(toggle){
|
||||
pm_.callPluginInitFunction(soPath);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> JamiPluginManager::listLoadedPlugins() const
|
||||
{
|
||||
std::vector<std::string> loadedSoPlugins = pm_.listLoadedPlugins();
|
||||
std::vector<std::string> loadedPlugins{};
|
||||
loadedPlugins.reserve(loadedSoPlugins.size());
|
||||
std::transform(loadedSoPlugins.begin(), loadedSoPlugins.end(), std::back_inserter(loadedPlugins),
|
||||
[this](const std::string& soPath) {
|
||||
return getRootPathFromSoPath(soPath);
|
||||
});
|
||||
return loadedPlugins;
|
||||
}
|
||||
|
||||
std::vector<std::map<std::string, std::string> > JamiPluginManager::getPluginPreferences(const std::string &rootPath)
|
||||
{
|
||||
const std::string preferenceFilePath = getPreferencesConfigFilePath(rootPath);
|
||||
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) {
|
||||
bool ok = Json::parseFromStream(rbuilder, file, &root, &errs);
|
||||
if(ok && root.isArray()) {
|
||||
for(int i=0; i< static_cast<int>(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();
|
||||
if(type != "None" && key != "None") {
|
||||
if(keys.find(key) == keys.end()) {
|
||||
const auto& preferenceAttributes = parsePreferenceConfig(jsonPreference, type);
|
||||
// If the parsing of the attributes was successful, commit the map and the key
|
||||
if(!preferenceAttributes.empty()) {
|
||||
preferences.push_back(std::move(preferenceAttributes));
|
||||
keys.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
JAMI_ERR() << "PluginPreferencesParser:: Failed to parse preferences.json for plugin: "
|
||||
<< preferenceFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
return preferences;
|
||||
}
|
||||
|
||||
bool JamiPluginManager::setPluginPreference(const std::string &rootPath, const std::string &key, const std::string &value)
|
||||
{
|
||||
bool returnValue = true;
|
||||
std::map<std::string, std::string> pluginPreferencesMap = getPluginPreferencesValuesMap(rootPath);
|
||||
// Using [] instead of insert to get insert or update effect
|
||||
pluginPreferencesMap[key] = value;
|
||||
|
||||
{
|
||||
const std::string preferencesValuesFilePath = pluginPreferencesValuesFilePath(rootPath);
|
||||
std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
|
||||
if(!fs.good()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
|
||||
msgpack::pack(fs, pluginPreferencesMap);
|
||||
} catch (const std::exception& e) {
|
||||
returnValue = false;
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> JamiPluginManager::getPluginPreferencesValuesMap(const std::string &rootPath)
|
||||
{
|
||||
const std::string preferencesValuesFilePath = pluginPreferencesValuesFilePath(rootPath);
|
||||
std::ifstream file(preferencesValuesFilePath, std::ios::binary);
|
||||
std::map<std::string, std::string> rmap;
|
||||
// If file is accessible
|
||||
if(file.good()) {
|
||||
std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool JamiPluginManager::resetPluginPreferencesValuesMap(const std::string &rootPath)
|
||||
{
|
||||
bool returnValue = true;
|
||||
std::map<std::string, std::string> pluginPreferencesMap{};
|
||||
|
||||
{
|
||||
const std::string preferencesValuesFilePath = pluginPreferencesValuesFilePath(rootPath);
|
||||
std::ofstream fs(preferencesValuesFilePath, std::ios::binary);
|
||||
if(!fs.good()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
std::lock_guard<std::mutex> guard(fileutils::getFileLock(preferencesValuesFilePath));
|
||||
msgpack::pack(fs, pluginPreferencesMap);
|
||||
} catch (const std::exception& e) {
|
||||
returnValue = false;
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> JamiPluginManager::readPluginManifestFromArchive(const std::string &jplPath)
|
||||
{
|
||||
try {
|
||||
return checkManifestValidity(archiver::readFileFromArchive(jplPath,"manifest.json"));
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> JamiPluginManager::parseManifestFile(const std::string &manifestFilePath)
|
||||
{
|
||||
std::ifstream file(manifestFilePath);
|
||||
if(file) {
|
||||
try {
|
||||
return checkManifestValidity(file);
|
||||
} catch (const std::exception& e) {
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void JamiPluginManager::registerServices()
|
||||
{
|
||||
// Register pluginPreferences
|
||||
pm_.registerService("getPluginPreferences", [this](const DLPlugin* plugin, void* data) {
|
||||
auto ppp = static_cast<std::map<std::string, std::string>*>(data);
|
||||
*ppp = getPluginPreferencesValuesMap(
|
||||
getRootPathFromSoPath(plugin->getPath()));
|
||||
return 0;
|
||||
});
|
||||
|
||||
pm_.registerService("getPluginDataPath", [this](const DLPlugin* plugin, void* data) {
|
||||
auto dataPath_ = static_cast<std::string*>(data);
|
||||
dataPath_->assign(dataPath(plugin->getPath()));
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
195
src/plugin/jamipluginmanager.h
Normal file
195
src/plugin/jamipluginmanager.h
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "noncopyable.h"
|
||||
#include "fileutils.h"
|
||||
#include "archiver.h"
|
||||
#include "pluginmanager.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
namespace jami {
|
||||
class JamiPluginManager
|
||||
{
|
||||
public:
|
||||
JamiPluginManager() {
|
||||
registerServices();
|
||||
}
|
||||
// TODO : improve getPluginDetails
|
||||
/**
|
||||
* @brief getPluginDetails
|
||||
* Parses a manifest file and returns :
|
||||
* The tuple (name, description, version, icon path, so path)
|
||||
* The icon should ideally be 192x192 pixels or better 512x512 pixels
|
||||
* In order to match with android specifications
|
||||
* https://developer.android.com/google-play/resources/icon-design-specifications
|
||||
* Saves the result in a map
|
||||
* @param plugin rootPath (folder of the plugin)
|
||||
* @return map where the keyset is {"name", "description", "iconPath"}
|
||||
*/
|
||||
std::map<std::string, std::string> getPluginDetails(const std::string& rootPath);
|
||||
|
||||
/**
|
||||
* @brief listAvailablePlugins
|
||||
* Lists available plugins with valid manifest files
|
||||
* @return list of plugin directory names
|
||||
*/
|
||||
std::vector<std::string> listAvailablePlugins();
|
||||
|
||||
/**
|
||||
* @brief installPlugin
|
||||
* Checks if the plugin has a valid manifest, installs the plugin if not previously installed
|
||||
* or if installing a newer version of it
|
||||
* If force is true, we force install the plugin
|
||||
* @param jplPath
|
||||
* @param force
|
||||
* @return + 0 if success
|
||||
* 100 if already installed with similar version
|
||||
* 200 if already installed with newer version
|
||||
* libarchive error codes otherwise
|
||||
*/
|
||||
int installPlugin(const std::string& jplPath, bool force);
|
||||
|
||||
/**
|
||||
* @brief uninstallPlugin
|
||||
* Checks if the plugin has a valid manifest then removes plugin folder
|
||||
* @param rootPath
|
||||
* @return 0 if success
|
||||
*/
|
||||
int uninstallPlugin(const std::string& rootPath);
|
||||
|
||||
/**
|
||||
* @brief loadPlugin
|
||||
* @param rootPath of the plugin folder
|
||||
* @return true is success
|
||||
*/
|
||||
bool loadPlugin(const std::string& rootPath);
|
||||
|
||||
/**
|
||||
* @brief unloadPlugin
|
||||
* @param rootPath of the plugin folder
|
||||
* @return true is success
|
||||
*/
|
||||
bool unloadPlugin(const std::string& rootPath);
|
||||
|
||||
/**
|
||||
* @brief togglePlugin
|
||||
* @param rootPath of the plugin folder
|
||||
* @param toggle: if true, register a new instance of the plugin
|
||||
* else, remove the existing instance
|
||||
* N.B: before adding a new instance, remove any existing one
|
||||
*/
|
||||
void togglePlugin(const std::string& rootPath, bool toggle);
|
||||
|
||||
/**
|
||||
* @brief listLoadedPlugins
|
||||
* @return vector of rootpaths of the loaded plugins
|
||||
*/
|
||||
std::vector<std::string> listLoadedPlugins() const;
|
||||
|
||||
std::vector<std::map<std::string,std::string>> getPluginPreferences(const std::string& rootPath);
|
||||
|
||||
bool setPluginPreference(const std::string& rootPath,
|
||||
const std::string& key,
|
||||
const std::string& value);
|
||||
|
||||
std::map<std::string,std::string>
|
||||
getPluginPreferencesValuesMap(const std::string& rootPath);
|
||||
|
||||
bool resetPluginPreferencesValuesMap(const std::string& rootPath);
|
||||
|
||||
private:
|
||||
|
||||
NON_COPYABLE(JamiPluginManager);
|
||||
|
||||
/**
|
||||
* @brief checkPluginValidity
|
||||
* Checks if the plugin has a manifest file with a name and a version
|
||||
* @return true if valid
|
||||
*/
|
||||
bool checkPluginValidity(const std::string& rootPath) {
|
||||
return !parseManifestFile(manifestPath(rootPath)).empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief readPluginManifestFromArchive
|
||||
* Reads the manifest file content without uncompressing the whole archive
|
||||
* Maps the manifest data to a map(string, string)
|
||||
* @param jplPath
|
||||
* @return manifest map
|
||||
*/
|
||||
std::map<std::string, std::string> readPluginManifestFromArchive(const std::string &jplPath);
|
||||
|
||||
/**
|
||||
* @brief parseManifestFile, parses the manifest file of an installed plugin
|
||||
* @param manifestFilePath
|
||||
* @return manifest map
|
||||
*/
|
||||
std::map<std::string, std::string> parseManifestFile(const std::string &manifestFilePath);
|
||||
|
||||
std::string manifestPath(const std::string& rootPath) {
|
||||
return rootPath + DIR_SEPARATOR_CH + "manifest.json";
|
||||
}
|
||||
|
||||
std::string getRootPathFromSoPath(const std::string& soPath) const {
|
||||
return soPath.substr(0,soPath.find_last_of(DIR_SEPARATOR_CH));
|
||||
}
|
||||
|
||||
std::string manifestPath(const std::string& rootPath) const {
|
||||
return rootPath + DIR_SEPARATOR_CH + "manifest.json";
|
||||
}
|
||||
|
||||
std::string dataPath(const std::string& pluginSoPath) const {
|
||||
return getRootPathFromSoPath(pluginSoPath) + DIR_SEPARATOR_CH + "data";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief getPreferencesConfigFilePath
|
||||
* Returns the plugin preferences config file path from the plugin root path
|
||||
* This is entirely defined by how the plugin files are structured
|
||||
* @param plugin rootPath
|
||||
* @return path of the preferences config
|
||||
*/
|
||||
std::string getPreferencesConfigFilePath(const std::string& rootPath) const {
|
||||
return rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH + "preferences.json";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief pluginPreferencesValuesFilePath
|
||||
* Returns the plugin preferences values file path from the plugin root path
|
||||
* This is entirely defined by how the plugin files are structured
|
||||
* @param plugin rootPath
|
||||
* @return path of the preferences values
|
||||
*/
|
||||
std::string pluginPreferencesValuesFilePath(const std::string& rootPath) const {
|
||||
return rootPath + DIR_SEPARATOR_CH + "preferences.msgpack";
|
||||
}
|
||||
|
||||
void registerServices();
|
||||
|
||||
private:
|
||||
PluginManager pm_;
|
||||
std::map<std::string, std::map<std::string, std::string>> pluginDetailsMap_;
|
||||
};
|
||||
}
|
||||
|
||||
|
80
src/plugin/pluginloader.h
Normal file
80
src/plugin/pluginloader.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Guillaume Roguez <guillaume.roguez@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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "jamiplugin.h"
|
||||
#include <dlfcn.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace jami {
|
||||
|
||||
class Plugin {
|
||||
public:
|
||||
virtual ~Plugin() = default;
|
||||
|
||||
static Plugin *load(const std::string &path, std::string &error);
|
||||
virtual void *getSymbol(const char *name) const = 0;
|
||||
virtual JAMI_PluginInitFunc getInitFunction() const {
|
||||
return reinterpret_cast<JAMI_PluginInitFunc>(
|
||||
getSymbol(JAMI_DYN_INIT_FUNC_NAME));
|
||||
}
|
||||
|
||||
protected:
|
||||
Plugin() = default;
|
||||
};
|
||||
|
||||
class DLPlugin : public Plugin {
|
||||
public:
|
||||
DLPlugin(void *handle, const std::string& path) : handle_(handle, ::dlclose), path_{path} {
|
||||
api_.context = this;
|
||||
}
|
||||
|
||||
virtual ~DLPlugin() { unload();}
|
||||
bool unload() {
|
||||
if(!handle_){
|
||||
return false;
|
||||
}
|
||||
return ::dlclose(handle_.release());
|
||||
}
|
||||
|
||||
|
||||
void *getSymbol(const char *name) const {
|
||||
if (!handle_)
|
||||
return nullptr;
|
||||
|
||||
return ::dlsym(handle_.get(), name);
|
||||
}
|
||||
|
||||
const std::string& getPath() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
public:
|
||||
void* apiContext_;
|
||||
JAMI_PluginAPI api_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<void, int (*)(void *)> handle_;
|
||||
const std::string path_;
|
||||
};
|
||||
|
||||
} // namespace jami
|
48
src/plugin/pluginloaderdl.cpp
Normal file
48
src/plugin/pluginloaderdl.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2018 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Guillaume Roguez <guillaume.roguez@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 "pluginloader.h"
|
||||
|
||||
namespace jami {
|
||||
|
||||
Plugin *Plugin::load(const std::string &path, std::string &error) {
|
||||
if (path.empty()) {
|
||||
error = "Empty path";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Clear any existing error
|
||||
::dlerror();
|
||||
|
||||
void *handle = ::dlopen(path.c_str(), RTLD_NOW);
|
||||
if (!handle) {
|
||||
error += "Failed to load \"" + path + '"';
|
||||
|
||||
std::string dlError = ::dlerror();
|
||||
if (dlError.size())
|
||||
error += " (" + dlError + ")";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new DLPlugin(handle, path);
|
||||
}
|
||||
|
||||
} // namespace jami
|
355
src/plugin/pluginmanager.cpp
Normal file
355
src/plugin/pluginmanager.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Guillaume Roguez <guillaume.roguez@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 "pluginmanager.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace jami {
|
||||
|
||||
PluginManager::PluginManager() {
|
||||
pluginApi_.context = reinterpret_cast<void *>(this);
|
||||
}
|
||||
|
||||
PluginManager::~PluginManager() {
|
||||
for (auto func : exitFuncVec_) {
|
||||
try {
|
||||
(*func)();
|
||||
} catch (...) {
|
||||
JAMI_ERR() << "Exception caught during plugin exit";
|
||||
}
|
||||
}
|
||||
|
||||
dynPluginMap_.clear();
|
||||
exactMatchMap_.clear();
|
||||
wildCardVec_.clear();
|
||||
exitFuncVec_.clear();
|
||||
}
|
||||
|
||||
bool PluginManager::load(const std::string &path) {
|
||||
// TODO: Resolve symbolic links and make path absolute
|
||||
|
||||
// Don't load the same dynamic library twice
|
||||
if (dynPluginMap_.find(path) != dynPluginMap_.end()) {
|
||||
JAMI_WARN() << "Plugin: already loaded";
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
std::unique_ptr<Plugin> plugin(Plugin::load(path, error));
|
||||
if (!plugin) {
|
||||
JAMI_ERR() << "Plugin: " << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &init_func = plugin->getInitFunction();
|
||||
if (!init_func) {
|
||||
JAMI_ERR() << "Plugin: no init symbol" << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!registerPlugin(plugin))
|
||||
return false;
|
||||
|
||||
dynPluginMap_[path] = std::move(plugin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginManager::unload(const std::string& path) {
|
||||
bool returnValue{false};
|
||||
destroyPluginComponents(path);
|
||||
PluginMap::iterator it = dynPluginMap_.find(path);
|
||||
if ( it != dynPluginMap_.end()) {
|
||||
dynPluginMap_.erase(it);
|
||||
returnValue = true;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
std::vector<std::string> PluginManager::listLoadedPlugins() const
|
||||
{
|
||||
std::vector<std::string> res{};
|
||||
for(const auto& pair : dynPluginMap_) {
|
||||
res.push_back(pair.first);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void PluginManager::destroyPluginComponents(const std::string &path)
|
||||
{
|
||||
auto itComponents = pluginComponentsMap_.find(path);
|
||||
if(itComponents != pluginComponentsMap_.end()) {
|
||||
for(const auto& pair : itComponents->second) {
|
||||
auto clcm = componentsLifeCycleManagers_.find(pair.first);
|
||||
if(clcm != componentsLifeCycleManagers_.end()) {
|
||||
clcm->second.destroyComponent(pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PluginManager::callPluginInitFunction(const std::string &path){
|
||||
bool returnValue{false};
|
||||
PluginMap::iterator it = dynPluginMap_.find(path);
|
||||
if ( it != dynPluginMap_.end()) {
|
||||
// Plugin found
|
||||
// Since the Plugin was found it is of type DLPlugin with a valid init symbol
|
||||
std::shared_ptr<DLPlugin> plugin = std::static_pointer_cast<DLPlugin>(it->second);
|
||||
const auto &initFunc = plugin->getInitFunction();
|
||||
JAMI_PluginExitFunc exitFunc = nullptr;
|
||||
|
||||
try {
|
||||
// Call Plugin Init function
|
||||
exitFunc = initFunc(&plugin->api_);
|
||||
} catch (const std::runtime_error &e) {
|
||||
JAMI_ERR() << e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!exitFunc) {
|
||||
JAMI_ERR() << "Plugin: init failed";
|
||||
returnValue = false;
|
||||
} else {
|
||||
returnValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
bool PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin) {
|
||||
// Here we know that Plugin is of type DLPlugin with a valid init symbol
|
||||
const auto &initFunc = plugin->getInitFunction();
|
||||
JAMI_PluginExitFunc exitFunc = nullptr;
|
||||
|
||||
DLPlugin* pluginPtr = static_cast<DLPlugin*>(plugin.get());
|
||||
|
||||
pluginPtr->apiContext_ = this;
|
||||
pluginPtr->api_.version = {JAMI_PLUGIN_ABI_VERSION, JAMI_PLUGIN_API_VERSION};
|
||||
pluginPtr->api_.registerObjectFactory = registerObjectFactory_;
|
||||
/**
|
||||
* Implements JAMI_PluginAPI.invokeService().
|
||||
* Must be C accessible.
|
||||
*/
|
||||
pluginPtr->api_.invokeService = [](const JAMI_PluginAPI *api,
|
||||
const char *name, void *data) {
|
||||
auto plugin = static_cast<DLPlugin*>(api->context);
|
||||
auto manager = reinterpret_cast<PluginManager *>(plugin->apiContext_);
|
||||
if (!manager) {
|
||||
JAMI_ERR() << "invokeService called with null plugin API";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return manager->invokeService(plugin, name, data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements JAMI_PluginAPI.invokeService().
|
||||
* Must be C accessible.
|
||||
*/
|
||||
pluginPtr->api_.manageComponent = [](const JAMI_PluginAPI* api, const char* name, void *data){
|
||||
auto plugin = static_cast<DLPlugin*>(api->context);
|
||||
auto manager = reinterpret_cast<PluginManager *>(plugin->apiContext_);
|
||||
if (!manager) {
|
||||
JAMI_ERR() << "createComponent called with null plugin API";
|
||||
return -1;
|
||||
} else if(!plugin){
|
||||
JAMI_ERR() << "createComponent called with null context";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return manager->manageComponent(plugin, name, data);
|
||||
};
|
||||
|
||||
try {
|
||||
exitFunc = initFunc(&pluginPtr->api_);
|
||||
} catch (const std::runtime_error &e) {
|
||||
JAMI_ERR() << e.what();
|
||||
}
|
||||
|
||||
if (!exitFunc) {
|
||||
tempExactMatchMap_.clear();
|
||||
tempWildCardVec_.clear();
|
||||
JAMI_ERR() << "Plugin: init failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
exitFuncVec_.push_back(exitFunc);
|
||||
exactMatchMap_.insert(tempExactMatchMap_.begin(), tempExactMatchMap_.end());
|
||||
wildCardVec_.insert(wildCardVec_.end(), tempWildCardVec_.begin(),
|
||||
tempWildCardVec_.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginManager::registerService(const std::string &name,
|
||||
ServiceFunction &&func) {
|
||||
services_[name] = std::forward<ServiceFunction>(func);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PluginManager::unRegisterService(const std::string &name) {
|
||||
services_.erase(name);
|
||||
}
|
||||
|
||||
int32_t PluginManager::invokeService(const DLPlugin* plugin, const std::string &name, void *data) {
|
||||
const auto &iterFunc = services_.find(name);
|
||||
if (iterFunc == services_.cend()) {
|
||||
JAMI_ERR() << "Services not found: " << name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto &func = iterFunc->second;
|
||||
|
||||
try {
|
||||
return func(plugin, data);
|
||||
} catch (const std::runtime_error &e) {
|
||||
JAMI_ERR() << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t PluginManager::manageComponent(const DLPlugin* plugin, const std::string& name, void *data) {
|
||||
const auto& iter = componentsLifeCycleManagers_.find(name);
|
||||
if(iter == componentsLifeCycleManagers_.end()) {
|
||||
JAMI_ERR() << "Component lifecycle manager not found: " << name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto& componentLifecycleManager = iter->second;
|
||||
|
||||
try {
|
||||
int32_t r = componentLifecycleManager.takeComponentOwnership(data);
|
||||
if(r == 0) {
|
||||
pluginComponentsMap_[plugin->getPath()].emplace_back(name,data);
|
||||
}
|
||||
return r;
|
||||
} catch(const std::runtime_error &e) {
|
||||
JAMI_ERR() << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* WARNING: exposed to plugins through JAMI_PluginAPI */
|
||||
bool PluginManager::registerObjectFactory(
|
||||
const char *type, const JAMI_PluginObjectFactory &factoryData) {
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
if (!factoryData.create || !factoryData.destroy)
|
||||
return false;
|
||||
|
||||
// Strict compatibility on ABI
|
||||
if (factoryData.version.abi != pluginApi_.version.abi)
|
||||
return false;
|
||||
|
||||
// Backward compatibility on API
|
||||
if (factoryData.version.api < pluginApi_.version.api)
|
||||
return false;
|
||||
|
||||
const std::string key(type);
|
||||
auto deleter = [factoryData](void *o) {
|
||||
factoryData.destroy(o, factoryData.closure);
|
||||
};
|
||||
ObjectFactory factory = {factoryData, deleter};
|
||||
|
||||
// wildcard registration?
|
||||
if (key == "*") {
|
||||
wildCardVec_.push_back(factory);
|
||||
return true;
|
||||
}
|
||||
|
||||
// fails on duplicate for exactMatch map
|
||||
if (exactMatchMap_.find(key) != exactMatchMap_.end())
|
||||
return false;
|
||||
|
||||
exactMatchMap_[key] = factory;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginManager::registerComponentManager(const std::string &name,
|
||||
ComponentFunction &&takeOwnership,
|
||||
ComponentFunction &&destroyComponent)
|
||||
{
|
||||
componentsLifeCycleManagers_[name] = {std::forward<ComponentFunction>(takeOwnership),
|
||||
std::forward<ComponentFunction>(destroyComponent)};
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<void, PluginManager::ObjectDeleter>
|
||||
PluginManager::createObject(const std::string &type) {
|
||||
if (type == "*")
|
||||
return {nullptr, nullptr};
|
||||
|
||||
JAMI_PluginObjectParams op = {
|
||||
/*.pluginApi = */ &pluginApi_,
|
||||
/*.type = */ type.c_str(),
|
||||
};
|
||||
|
||||
// Try to find an exact match
|
||||
const auto &factoryIter = exactMatchMap_.find(type);
|
||||
if (factoryIter != exactMatchMap_.end()) {
|
||||
const auto &factory = factoryIter->second;
|
||||
auto object = factory.data.create(&op, factory.data.closure);
|
||||
if (object)
|
||||
return {object, factory.deleter};
|
||||
}
|
||||
|
||||
// Try to find a wildcard match
|
||||
for (const auto &factory : wildCardVec_) {
|
||||
auto object = factory.data.create(&op, factory.data.closure);
|
||||
if (object) {
|
||||
// promote registration to exactMatch_
|
||||
// (but keep also wildcard registration for other object types)
|
||||
int32_t res = registerObjectFactory(op.type, factory.data);
|
||||
if (res < 0) {
|
||||
JAMI_ERR() << "failed to register object " << op.type;
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
return {object, factory.deleter};
|
||||
}
|
||||
}
|
||||
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
/* WARNING: exposed to plugins through JAMI_PluginAPI */
|
||||
int32_t PluginManager::registerObjectFactory_(const JAMI_PluginAPI *api,
|
||||
const char *type, void *data) {
|
||||
auto manager = reinterpret_cast<PluginManager *>(api->context);
|
||||
if (!manager) {
|
||||
JAMI_ERR() << "registerObjectFactory called with null plugin API";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
JAMI_ERR() << "registerObjectFactory called with null factory data";
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto factory = reinterpret_cast<JAMI_PluginObjectFactory *>(data);
|
||||
return manager->registerObjectFactory(type, *factory) ? 0 : -1;
|
||||
}
|
||||
|
||||
} // namespace jami
|
192
src/plugin/pluginmanager.h
Normal file
192
src/plugin/pluginmanager.h
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Guillaume Roguez <guillaume.roguez@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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "noncopyable.h"
|
||||
#include "jamiplugin.h"
|
||||
#include "pluginloader.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace jami {
|
||||
|
||||
class Plugin;
|
||||
|
||||
class PluginManager {
|
||||
public:
|
||||
using ObjectDeleter = std::function<void(void *)>;
|
||||
using ServiceFunction = std::function<int32_t(const DLPlugin*, void *)>;
|
||||
using ComponentFunction = std::function<int32_t(void*)>;
|
||||
//A vector to a pair<componentType, componentPtr>
|
||||
using ComponentTypePtrVector = std::vector<std::pair<std::string, void*>>;
|
||||
private:
|
||||
struct ObjectFactory {
|
||||
JAMI_PluginObjectFactory data;
|
||||
ObjectDeleter deleter;
|
||||
};
|
||||
|
||||
struct ComponentLifeCycleManager{
|
||||
ComponentFunction takeComponentOwnership;
|
||||
ComponentFunction destroyComponent;
|
||||
};
|
||||
|
||||
using PluginMap = std::map<std::string, std::shared_ptr<Plugin>>;
|
||||
using PluginComponentsMap = std::map<std::string, ComponentTypePtrVector>;
|
||||
using ExitFuncVec = std::vector<JAMI_PluginExitFunc>;
|
||||
using ObjectFactoryVec = std::vector<ObjectFactory>;
|
||||
using ObjectFactoryMap = std::map<std::string, ObjectFactory>;
|
||||
|
||||
public:
|
||||
PluginManager();
|
||||
~PluginManager();
|
||||
|
||||
/**
|
||||
* Load a dynamic plugin by filename.
|
||||
*
|
||||
* @param path fully qualified pathname on a loadable plugin binary
|
||||
* @return true if success
|
||||
*/
|
||||
bool load(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief unloads the plugin with pathname path
|
||||
* @param path
|
||||
* @return true if success
|
||||
*/
|
||||
bool unload(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief listLoadedPlugins
|
||||
* @return vector of strings of so files of the loaded plugins
|
||||
*/
|
||||
std::vector<std::string> listLoadedPlugins() const;
|
||||
|
||||
/**
|
||||
* @brief destroyPluginComponents
|
||||
* @param path
|
||||
*/
|
||||
void destroyPluginComponents(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief callPluginInitFunction
|
||||
* @param path: plugin path used as an id in the plugin map
|
||||
* @return true if succes
|
||||
*/
|
||||
bool callPluginInitFunction(const std::string& path);
|
||||
/**
|
||||
* Register a plugin.
|
||||
*
|
||||
* @param initFunc plugin init function
|
||||
* @return true if success
|
||||
*/
|
||||
bool registerPlugin(std::unique_ptr<Plugin>& plugin);
|
||||
|
||||
/**
|
||||
* Register a new service for plugin.
|
||||
*
|
||||
* @param name The service name
|
||||
* @param func The function called by Ring_PluginAPI.invokeService
|
||||
* @return true if success
|
||||
*/
|
||||
bool registerService(const std::string &name, ServiceFunction &&func);
|
||||
|
||||
void unRegisterService(const std::string &name);
|
||||
|
||||
/**
|
||||
* Register a new public objects factory.
|
||||
*
|
||||
* @param type unique identifier of the object
|
||||
* @param params object factory details
|
||||
* @return true if success
|
||||
*
|
||||
* Note: type can be the string "*" meaning that the factory
|
||||
* will be called if no exact match factories are found for a given type.
|
||||
*/
|
||||
bool registerObjectFactory(const char *type,
|
||||
const JAMI_PluginObjectFactory &factory);
|
||||
/**
|
||||
* @brief registerComponentManager
|
||||
* Registers a component manager that will have two functions, one to take
|
||||
* ownership of the component and the other one to destroy it
|
||||
* @param name : name of the component manager
|
||||
* @param takeOwnership function that takes ownership on created objet in memory
|
||||
* @param destroyComponent desotry the component
|
||||
* @return true if success
|
||||
*/
|
||||
bool registerComponentManager(const std::string& name, ComponentFunction&& takeOwnership,
|
||||
ComponentFunction&& destroyComponent);
|
||||
|
||||
/**
|
||||
* Create a new plugin's exported object.
|
||||
*
|
||||
* @param type unique identifier of the object to create.
|
||||
* @return unique pointer on created object.
|
||||
*/
|
||||
std::unique_ptr<void, ObjectDeleter> createObject(const std::string &type);
|
||||
|
||||
private:
|
||||
NON_COPYABLE(PluginManager);
|
||||
|
||||
/**
|
||||
* Implements JAMI_PluginAPI.registerObjectFactory().
|
||||
* Must be C accessible.
|
||||
*/
|
||||
static int32_t registerObjectFactory_(const JAMI_PluginAPI *api,
|
||||
const char *type, void *data);
|
||||
int32_t invokeService(const DLPlugin* plugin, const std::string &name, void *data);
|
||||
|
||||
int32_t manageComponent(const DLPlugin* plugin, const std::string& name, void *data);
|
||||
|
||||
std::mutex mutex_{};
|
||||
JAMI_PluginAPI pluginApi_ = {
|
||||
{JAMI_PLUGIN_ABI_VERSION, JAMI_PLUGIN_API_VERSION},
|
||||
nullptr, // set by PluginManager constructor
|
||||
registerObjectFactory_,
|
||||
nullptr,
|
||||
nullptr
|
||||
};
|
||||
PluginMap dynPluginMap_{}; // Only dynamic loaded plugins
|
||||
ExitFuncVec exitFuncVec_{};
|
||||
ObjectFactoryMap exactMatchMap_{};
|
||||
ObjectFactoryVec wildCardVec_{};
|
||||
|
||||
// Storage used during plugin initialisation.
|
||||
// Will be copied into previous ones only if the initialisation success.
|
||||
ObjectFactoryMap tempExactMatchMap_{};
|
||||
ObjectFactoryVec tempWildCardVec_{};
|
||||
|
||||
// registered services
|
||||
std::map<std::string, ServiceFunction> services_{};
|
||||
// registered component lifecycle managers
|
||||
std::map<std::string, ComponentLifeCycleManager> componentsLifeCycleManagers_ {};
|
||||
|
||||
// references to plugins components, used for cleanup
|
||||
PluginComponentsMap pluginComponentsMap_ {};
|
||||
};
|
||||
|
||||
} // namespace jami
|
Reference in New Issue
Block a user