Skip to content
Snippets Groups Projects
Commit 6659db2c authored by Aline Gondim Santos's avatar Aline Gondim Santos Committed by Sébastien Blin
Browse files

Plugins: Code documentation

GitLab: #422
Change-Id: Id94eb245d9675d305a0d05da76e62bcd245525ec
parent cf958233
No related branches found
No related tags found
No related merge requests found
Showing
with 1014 additions and 507 deletions
......@@ -274,9 +274,10 @@ if conf.get('ENABLE_PLUGIN')
'plugin/callservicesmanager.cpp',
'plugin/chatservicesmanager.cpp',
'plugin/jamipluginmanager.cpp',
'plugin/pluginloaderdl.cpp',
'plugin/pluginloader.cpp',
'plugin/pluginmanager.cpp',
'plugin/pluginpreferencesutils.cpp'
'plugin/pluginpreferencesutils.cpp',
'plugin/pluginsutils.cpp'
)
libjami_dependencies += [deplibarchive, depdl]
endif
......
......@@ -3,9 +3,10 @@
################################################################################
list (APPEND Source_Files__plugin
"${CMAKE_CURRENT_SOURCE_DIR}/jamipluginmanager.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginloaderdl.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginloader.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginmanager.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginpreferencesutils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginsutils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/callservicesmanager.h"
"${CMAKE_CURRENT_SOURCE_DIR}/callservicesmanager.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/chatservicesmanager.h"
......@@ -13,6 +14,7 @@ list (APPEND Source_Files__plugin
"${CMAKE_CURRENT_SOURCE_DIR}/chathandler.h"
"${CMAKE_CURRENT_SOURCE_DIR}/jamiplugin.h"
"${CMAKE_CURRENT_SOURCE_DIR}/jamipluginmanager.h"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginsutils.h"
"${CMAKE_CURRENT_SOURCE_DIR}/mediahandler.h"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginloader.h"
"${CMAKE_CURRENT_SOURCE_DIR}/pluginmanager.h"
......
......@@ -12,13 +12,15 @@ noinst_HEADERS = \
pluginloader.h \
pluginmanager.h \
pluginpreferencesutils.h \
streamdata.h
streamdata.h \
pluginsutils.h
libplugin_la_SOURCES = \
jamipluginmanager.cpp \
pluginloaderdl.cpp \
pluginloader.cpp \
pluginmanager.cpp \
pluginpreferencesutils.cpp \
pluginsutils.cpp \
chatservicesmanager.cpp \
callservicesmanager.cpp
......
/**
* Copyright (C)2020-2021 Savoir-faire Linux Inc.
/*
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -19,36 +19,45 @@
*/
#include "callservicesmanager.h"
#include "logger.h"
#include "sip/sipcall.h"
#include "fileutils.h"
#include "pluginmanager.h"
#include "pluginpreferencesutils.h"
#include "manager.h"
#include "sip/sipcall.h"
#include "fileutils.h"
#include "logger.h"
namespace jami {
CallServicesManager::CallServicesManager(PluginManager& pm)
CallServicesManager::CallServicesManager(PluginManager& pluginManager)
{
registerComponentsLifeCycleManagers(pm);
registerComponentsLifeCycleManagers(pluginManager);
}
CallServicesManager::~CallServicesManager()
{
callMediaHandlers_.clear();
callAVsubjects_.clear();
mediaHandlerToggled_.clear();
}
void
CallServicesManager::createAVSubject(const StreamData& data, AVSubjectSPtr subject)
{
/// callAVsubjects_ emplaces data and subject with callId key to easy of access
/// When call is ended, subjects from this call are erased.
// callAVsubjects_ emplaces data and subject with callId key to easy of access
// When call is ended, subjects from this call are erased.
callAVsubjects_[data.id].emplace_back(data, subject);
// Search for activation flag.
for (auto& callMediaHandler : callMediaHandlers_) {
std::size_t found = callMediaHandler->id().find_last_of(DIR_SEPARATOR_CH);
// toggle is true if we should automatically activate the MediaHandler.
bool toggle = PluginPreferencesUtils::getAlwaysPreference(
callMediaHandler->id().substr(0, found),
callMediaHandler->getCallMediaHandlerDetails().at("name"));
// toggle may be overwritten if the MediaHandler was previously activated/deactivated
// for the given call.
for (const auto& toggledMediaHandlerPair : mediaHandlerToggled_[data.id]) {
if (toggledMediaHandlerPair.first == (uintptr_t) callMediaHandler.get()) {
toggle = toggledMediaHandlerPair.second;
......@@ -57,8 +66,12 @@ CallServicesManager::createAVSubject(const StreamData& data, AVSubjectSPtr subje
}
if (toggle)
#ifndef __ANDROID__
// If activation is expected, we call activation function
toggleCallMediaHandler((uintptr_t) callMediaHandler.get(), data.id, true);
#else
// Due to Android's camera activation process, we don't automaticaly
// activate the MediaHandler here. But we set it as active
// and the client-android will handle its activation.
mediaHandlerToggled_[data.id].insert({(uintptr_t) callMediaHandler.get(), true});
#endif
}
......@@ -71,14 +84,16 @@ CallServicesManager::clearAVSubject(const std::string& callId)
}
void
CallServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
CallServicesManager::registerComponentsLifeCycleManagers(PluginManager& pluginManager)
{
auto registerCallMediaHandler = [this](void* data) {
// registerMediaHandler may be called by the PluginManager upon loading a plugin.
auto registerMediaHandler = [this](void* data) {
CallMediaHandlerPtr ptr {(static_cast<CallMediaHandler*>(data))};
if (!ptr)
return -1;
std::size_t found = ptr->id().find_last_of(DIR_SEPARATOR_CH);
// Adding preference that tells us to automatically activate a MediaHandler.
PluginPreferencesUtils::addAlwaysHandlerPreference(ptr->getCallMediaHandlerDetails().at(
"name"),
ptr->id().substr(0, found));
......@@ -86,6 +101,7 @@ CallServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
return 0;
};
// unregisterMediaHandler may be called by the PluginManager while unloading.
auto unregisterMediaHandler = [this](void* data) {
auto handlerIt = std::find_if(callMediaHandlers_.begin(),
callMediaHandlers_.end(),
......@@ -103,6 +119,7 @@ CallServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
== (uintptr_t) handlerIt->get()
&& handlerIdPair.second;
});
// If MediaHandler we're trying to destroy is currently in use, we deactivate it.
if (handlerId != toggledList.second.end())
toggleCallMediaHandler((*handlerId).first, toggledList.first, false);
}
......@@ -111,9 +128,10 @@ CallServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
return true;
};
pm.registerComponentManager("CallMediaHandlerManager",
registerCallMediaHandler,
unregisterMediaHandler);
// Services are registered to the PluginManager.
pluginManager.registerComponentManager("CallMediaHandlerManager",
registerMediaHandler,
unregisterMediaHandler);
}
std::vector<std::string>
......@@ -154,6 +172,7 @@ CallServicesManager::getCallMediaHandlerDetails(const std::string& mediaHandlerI
bool
CallServicesManager::isVideoType(const CallMediaHandlerPtr& mediaHandler)
{
// "dataType" is known from the MediaHandler implementation.
const auto& details = mediaHandler->getCallMediaHandlerDetails();
const auto& it = details.find("dataType");
if (it != details.end()) {
......@@ -161,12 +180,15 @@ CallServicesManager::isVideoType(const CallMediaHandlerPtr& mediaHandler)
std::istringstream(it->second) >> status;
return status;
}
// If there is no "dataType" returned, it's safer to return True and allow
// sender to restart.
return true;
}
bool
CallServicesManager::isAttached(const CallMediaHandlerPtr& mediaHandler)
{
// "attached" is known from the MediaHandler implementation.
const auto& details = mediaHandler->getCallMediaHandlerDetails();
const auto& it = details.find("attached");
if (it != details.end()) {
......@@ -184,7 +206,7 @@ CallServicesManager::getCallMediaHandlerStatus(const std::string& callId)
const auto& it = mediaHandlerToggled_.find(callId);
if (it != mediaHandlerToggled_.end())
for (const auto& mediaHandlerId : it->second)
if (mediaHandlerId.second)
if (mediaHandlerId.second) // Only return active MediaHandler ids
ret.emplace_back(std::to_string(mediaHandlerId.first));
return ret;
}
......@@ -230,25 +252,23 @@ CallServicesManager::toggleCallMediaHandler(const uintptr_t mediaHandlerId,
bool applyRestart = false;
for (auto subject : callAVsubjects_[callId]) {
if (subject.first.id == callId) {
auto handlerIt = std::find_if(callMediaHandlers_.begin(),
callMediaHandlers_.end(),
[mediaHandlerId](CallMediaHandlerPtr& handler) {
return ((uintptr_t) handler.get() == mediaHandlerId);
});
auto handlerIt = std::find_if(callMediaHandlers_.begin(),
callMediaHandlers_.end(),
[mediaHandlerId](CallMediaHandlerPtr& handler) {
return ((uintptr_t) handler.get() == mediaHandlerId);
});
if (handlerIt != callMediaHandlers_.end()) {
if (toggle) {
notifyAVSubject((*handlerIt), subject.first, subject.second);
if (isAttached((*handlerIt)))
handlers[mediaHandlerId] = true;
} else {
(*handlerIt)->detach();
handlers[mediaHandlerId] = false;
}
if (subject.first.type == StreamType::video && isVideoType((*handlerIt)))
applyRestart = true;
if (handlerIt != callMediaHandlers_.end()) {
if (toggle) {
notifyAVSubject((*handlerIt), subject.first, subject.second);
if (isAttached((*handlerIt)))
handlers[mediaHandlerId] = true;
} else {
(*handlerIt)->detach();
handlers[mediaHandlerId] = false;
}
if (subject.first.type == StreamType::video && isVideoType((*handlerIt)))
applyRestart = true;
}
}
#ifndef __ANDROID__
......
/**
* Copyright (C)2020-2021 Savoir-faire Linux Inc.
/*
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -20,98 +20,123 @@
#pragma once
#include "noncopyable.h"
#include "logger.h"
#include "manager.h"
#include "sip/sipcall.h"
#include "pluginmanager.h"
#include "streamdata.h"
#include "mediahandler.h"
#include "streamdata.h"
#include "noncopyable.h"
#include <list>
#include <set>
#include <map>
#include <tuple>
namespace jami {
using MediaHandlerPtr = std::unique_ptr<MediaHandler>;
class PluginManager;
using CallMediaHandlerPtr = std::unique_ptr<CallMediaHandler>;
using AVSubjectSPtr = std::weak_ptr<Observable<AVFrame*>>;
/**
* @brief This class provides the interface between loaded MediaHandlers
* and call's audio/video streams. Besides it:
* (1) stores pointers to all loaded MediaHandlers;
* (2) stores pointers to available streams subjects, and;
* (3) lists MediaHandler state with respect to each call. In other words,
* for a given call, we store if a MediaHandler is active or not.
*/
class CallServicesManager
{
public:
CallServicesManager(PluginManager& pm);
/**
* unload all media handlers
**/
* @brief Constructor registers MediaHandler API services to the PluginManager
* instance. These services will store MediaHandler pointers or clean them
* from the Plugin System once a plugin is loaded or unloaded.
* @param pluginManager
*/
CallServicesManager(PluginManager& pluginManager);
~CallServicesManager();
NON_COPYABLE(CallServicesManager);
/**
* @brief createAVSubject
* @brief Stores a AV stream subject with StreamData properties. During the storage process,
* if a MediaHandler is supposed to be activated for the call to which the subject is
* related, the activation function is called.
* @param data
* Creates an av frame subject with properties StreamData
* @param subject
*/
void createAVSubject(const StreamData& data, AVSubjectSPtr subject);
void clearAVSubject(const std::string& callId);
/**
* @brief registerComponentsLifeCycleManagers
* Exposes components life cycle managers to the main API
* @brief Clears all stream subjects related to the callId.
* @param callId
*/
void registerComponentsLifeCycleManagers(PluginManager& pm);
void clearAVSubject(const std::string& callId);
/**
* @brief getCallMediaHandlers
* List all call media handlers
* @return
* @brief List all MediaHandlers available.
* @return Vector with stored MediaHandlers pointers.
*/
std::vector<std::string> getCallMediaHandlers();
/**
* @brief toggleCallMediaHandler
* Toggle CallMediaHandler, if on, notify with new subjects
* if off, detach it
* @param mediaHandler ID handler ID
* @param callId call ID
* @param toggle notify with new subjects if true, detach if false.
*
* In the case when the mediaHandler receives a hardware format
* frame and converts it to main memory, we need to restart the
* sender to unlink ours encoder and decoder.
*
* When we deactivate a mediaHandler, we try to relink the encoder
* @brief (De)Activates a given MediaHandler to a given call.
* If the MediaHandler receives video frames from a hardware decoder,
* we need to restart the sender to unlink our encoder and decoder.
* When we deactivate a MediaHandler, we try to relink the encoder
* and decoder by restarting the sender.
*
* @param mediaHandlerId
* @param callId
* @param toggle notify with new subjects if true, detach if false.
*/
void toggleCallMediaHandler(const std::string& mediaHandlerId,
const std::string& callId,
const bool toggle);
/**
* @brief getCallMediaHandlerDetails
* @param id of the call media handler
* @return map of Call Media Handler Details
* @brief Returns details Map from MediaHandler implementation.
* @param mediaHandlerIdStr
* @return Details map from the MediaHandler implementation
*/
std::map<std::string, std::string> getCallMediaHandlerDetails(
const std::string& mediaHandlerIdStr);
bool isVideoType(const CallMediaHandlerPtr& mediaHandler);
bool isAttached(const CallMediaHandlerPtr& mediaHandler);
/**
* @brief Returns a list of active MediaHandlers for a given call.
* @param callId
* @return Vector with active MediaHandler ids for a given call.
*/
std::vector<std::string> getCallMediaHandlerStatus(const std::string& callId);
/**
* @brief Sets a preference that may be changed while MediaHandler is active.
* @param key
* @param value
* @param rootPath
* @return False if preference was changed.
*/
bool setPreference(const std::string& key,
const std::string& value,
const std::string& rootPath);
/**
* @brief Removes call from mediaHandlerToggled_ mapping.
* @param callId
*/
void clearCallHandlerMaps(const std::string& callId);
private:
/**
* @brief notifyAVSubject
* @brief Exposes MediaHandlers' life cycle managers services to the main API.
* @param pluginManager
*/
void registerComponentsLifeCycleManagers(PluginManager& pluginManager);
/**
* @brief Calls MediaHandler API function that attaches a data process to the given
* AV stream.
* @param callMediaHandlerPtr
* @param data
* @param subject
......@@ -125,20 +150,32 @@ private:
const bool toggle);
/**
* @brief callMediaHandlers_
* Components that a plugin can register through registerCallMediaHandler service
* These objects can then be notified with notifySubject
* whenever there is a new CallAVSubject like a video receive
* @brief Checks if the MediaHandler being (de)activated expects a video stream.
* It's used to reduce restartSender call.
* @param mediaHandler
* @return True if a MediaHandler expects a video stream.
*/
bool isVideoType(const CallMediaHandlerPtr& mediaHandler);
/**
* @brief Checks if the MediaHandler was properly attached to a AV stream.
* It's used to avoid saving wrong MediaHandler status.
* @param mediaHandler
* @return True if a MediaHandler is attached to a AV stream.
*/
bool isAttached(const CallMediaHandlerPtr& mediaHandler);
// Components that a plugin can register through registerMediaHandler service.
// These objects can then be activated with toggleCallMediaHandler.
std::list<CallMediaHandlerPtr> callMediaHandlers_;
/// When there is a SIPCall, AVSubjects are created there.
/// Here we store their references in order to make them interact with MediaHandlers.
/// For easy access they are mapped with the callId they belong to.
// When there is a SIPCall, AVSubjects are created there.
// Here we store their references in order to make them interact with MediaHandlers.
// For easy access they are mapped with the call they belong to.
std::map<std::string, std::list<std::pair<const StreamData, AVSubjectSPtr>>> callAVsubjects_;
/// Component that stores MediaHandlers' status for each existing call.
/// A map of callIds and MediaHandler-status pairs.
// Component that stores MediaHandlers' status for each existing call.
// A map of callIds and MediaHandler-status pairs.
std::map<std::string, std::map<uintptr_t, bool>> mediaHandlerToggled_;
};
} // namespace jami
/*
* Copyright (C) 2020 Savoir-faire Linux Inc.
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -30,28 +30,64 @@ namespace jami {
using pluginMessagePtr = std::shared_ptr<JamiMessage>;
using chatSubjectPtr = std::shared_ptr<PublishObservable<pluginMessagePtr>>;
/**
* @brief This abstract class is an API we need to implement from plugin side.
* In other words, a plugin functionality that plays with messages, must start
* from the implementation of this class.
*/
class ChatHandler
{
public:
virtual ~ChatHandler() {}
/**
* @brief Should attach a chat subject (Observable) and the plugin data process (Observer).
* @param subjectConnection accountId, peerId pair
* @param subject chat Subject pointer
*/
virtual void notifyChatSubject(std::pair<std::string, std::string>& subjectConnection,
chatSubjectPtr subject)
= 0;
/**
* @brief Returns a map with handler's name, iconPath, and pluginId.
*/
virtual std::map<std::string, std::string> getChatHandlerDetails() = 0;
/**
* @brief Should detach a chat subject (Observable) and the plugin data process (Observer).
* @param subject chat subject pointer
*/
virtual void detach(chatSubjectPtr subject) = 0;
/**
* @brief If a preference can be changed without the need to reload the plugin, it
* should be done through this function.
* @param key
* @param value
*/
virtual void setPreferenceAttribute(const std::string& key, const std::string& value) = 0;
/**
* @brief If a preference can be changed without the need to reload the plugin, this function
* should return True.
* @param key
* @return True if preference can be changed through setPreferenceAttribute method.
*/
virtual bool preferenceMapHasKey(const std::string& key) = 0;
/**
* @brief id
* The id is the path of the plugin that created this MediaHandler
* @return
* @brief Returns the dataPath of the plugin that created this ChatHandler.
*/
std::string id() const { return id_; }
/**
* @brief Should be called by the ChatHandler creator to set the plugins id_ variable.
*/
virtual void setId(const std::string& id) final { id_ = id; }
private:
// Is the dataPath of the plugin that created this ChatHandler.
std::string id_;
};
} // namespace jami
......@@ -17,22 +17,24 @@
*/
#include "chatservicesmanager.h"
#include "pluginmanager.h"
#include "logger.h"
#include "manager.h"
#include "fileutils.h"
namespace jami {
ChatServicesManager::ChatServicesManager(PluginManager& pm)
ChatServicesManager::ChatServicesManager(PluginManager& pluginManager)
{
registerComponentsLifeCycleManagers(pm);
registerChatService(pm);
registerComponentsLifeCycleManagers(pluginManager);
registerChatService(pluginManager);
PluginPreferencesUtils::getAllowDenyListPreferences(allowDenyList_);
}
void
ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pluginManager)
{
// registerChatHandler may be called by the PluginManager upon loading a plugin.
auto registerChatHandler = [this](void* data) {
ChatHandlerPtr ptr {(static_cast<ChatHandler*>(data))};
......@@ -40,12 +42,14 @@ ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
return -1;
handlersNameMap_[ptr->getChatHandlerDetails().at("name")] = (uintptr_t) ptr.get();
std::size_t found = ptr->id().find_last_of(DIR_SEPARATOR_CH);
// Adding preference that tells us to automatically activate a ChatHandler.
PluginPreferencesUtils::addAlwaysHandlerPreference(ptr->getChatHandlerDetails().at("name"),
ptr->id().substr(0, found));
chatHandlers_.emplace_back(std::move(ptr));
return 0;
};
// unregisterChatHandler may be called by the PluginManager while unloading.
auto unregisterChatHandler = [this](void* data) {
auto handlerIt = std::find_if(chatHandlers_.begin(),
chatHandlers_.end(),
......@@ -60,6 +64,7 @@ ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
[this, handlerIt](uintptr_t handlerId) {
return (handlerId == (uintptr_t) handlerIt->get());
});
// If ChatHandler we're trying to destroy is currently in use, we deactivate it.
if (handlerId != toggledList.second.end()) {
(*handlerIt)->detach(chatSubjects_[toggledList.first]);
toggledList.second.erase(handlerId);
......@@ -71,19 +76,24 @@ ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pm)
return true;
};
pm.registerComponentManager("ChatHandlerManager", registerChatHandler, unregisterChatHandler);
// Services are registered to the PluginManager.
pluginManager.registerComponentManager("ChatHandlerManager",
registerChatHandler,
unregisterChatHandler);
}
void
ChatServicesManager::registerChatService(PluginManager& pm)
ChatServicesManager::registerChatService(PluginManager& pluginManager)
{
// sendTextMessage is a service that allows plugins to send a message in a conversation.
auto sendTextMessage = [this](const DLPlugin*, void* data) {
auto cm = static_cast<JamiMessage*>(data);
jami::Manager::instance().sendTextMessage(cm->accountId, cm->peerId, cm->data, true);
return 0;
};
pm.registerService("sendTextMessage", sendTextMessage);
// Services are registered to the PluginManager.
pluginManager.registerService("sendTextMessage", sendTextMessage);
}
std::vector<std::string>
......@@ -98,32 +108,39 @@ ChatServicesManager::getChatHandlers()
}
void
ChatServicesManager::publishMessage(pluginMessagePtr& cm)
ChatServicesManager::publishMessage(pluginMessagePtr& message)
{
if (cm->fromPlugin)
if (message->fromPlugin)
return;
std::pair<std::string, std::string> mPair(cm->accountId, cm->peerId);
std::pair<std::string, std::string> mPair(message->accountId, message->peerId);
auto& handlers = chatHandlerToggled_[mPair];
auto& chatAllowDenySet = allowDenyList_[mPair];
// Search for activation flag.
for (auto& chatHandler : chatHandlers_) {
std::string chatHandlerName = chatHandler->getChatHandlerDetails().at("name");
std::size_t found = chatHandler->id().find_last_of(DIR_SEPARATOR_CH);
// toggle is true if we should automatically activate the ChatHandler.
bool toggle = PluginPreferencesUtils::getAlwaysPreference(chatHandler->id().substr(0, found),
chatHandlerName);
// toggle is overwritten if we have previously activated/deactivated the ChatHandler
// for the given conversation.
auto allowedIt = chatAllowDenySet.find(chatHandlerName);
if (allowedIt != chatAllowDenySet.end())
toggle = (*allowedIt).second;
bool toggled = handlers.find((uintptr_t) chatHandler.get()) != handlers.end();
if (toggle || toggled) {
// Creates chat subjects if it doesn't exist yet.
chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>());
if (!toggled) {
// If activation is expected, and not yet performed, we perform activation
handlers.insert((uintptr_t) chatHandler.get());
chatHandler->notifyChatSubject(mPair, chatSubjects_[mPair]);
chatAllowDenySet[chatHandlerName] = true;
PluginPreferencesUtils::setAllowDenyListPreferences(allowDenyList_);
}
chatSubjects_[mPair]->publish(cm);
// Finally we feed Chat subject with the message.
chatSubjects_[mPair]->publish(message);
}
}
}
......@@ -159,7 +176,7 @@ ChatServicesManager::getChatHandlerStatus(const std::string& accountId, const st
std::vector<std::string> ret;
if (it != allowDenyList_.end()) {
for (const auto& chatHandlerName : it->second)
if (chatHandlerName.second)
if (chatHandlerName.second) // We only return active ChatHandler ids
ret.emplace_back(std::to_string(handlersNameMap_.at(chatHandlerName.first)));
}
......
......@@ -18,66 +18,136 @@
#pragma once
#include "noncopyable.h"
#include "pluginmanager.h"
#include "chathandler.h"
#include "pluginpreferencesutils.h"
namespace jami {
class PluginManager;
using ChatHandlerPtr = std::unique_ptr<ChatHandler>;
/**
* @brief This class provides the interface between loaded ChatHandlers
* and conversation messages. Besides it:
* (1) stores pointers to all loaded ChatHandlers;
* (2) stores pointers to availables chat subjects, and;
* (3) lists ChatHandler state with respect to each accountId, peerId pair. In other words,
* for a given accountId, peerId pair, we store if a ChatHandler is active or not.
*/
class ChatServicesManager
{
public:
ChatServicesManager(PluginManager& pm);
/**
* @brief Constructor registers ChatHandler API services to the PluginManager
* instance. These services will store ChatHandler pointers, clean them
* from the Plugin System once a plugin is loaded or unloaded, or yet allows
* the plugins to send a message to a conversation.
* @param pluginManager
*/
ChatServicesManager(PluginManager& pluginManager);
NON_COPYABLE(ChatServicesManager);
void registerComponentsLifeCycleManagers(PluginManager& pm);
void registerChatService(PluginManager& pm);
/**
* @brief List all ChatHandlers available.
* @return Vector of stored ChatHandlers pointers.
*/
std::vector<std::string> getChatHandlers();
void publishMessage(pluginMessagePtr& cm);
/**
* @brief Publishes every message sent or received in a conversation that has (or should have)
* an active ChatHandler.
* @param message
*/
void publishMessage(pluginMessagePtr& message);
/**
* @brief If an account is unregistered or a contact is erased, we clear all chat subjects
* related to that accountId or to the accountId, peerId pair.
* @param accountId
* @param peerId
*/
void cleanChatSubjects(const std::string& accountId, const std::string& peerId = "");
/**
* @brief Activates or deactivate a given ChatHandler to a given accountId, peerId pair.
* @param ChatHandlerId
* @param accountId
* @param peerId
* @param toggle Notify with new subjects if true, detach if false.
*/
void toggleChatHandler(const std::string& chatHandlerId,
const std::string& accountId,
const std::string& peerId,
const bool toggle);
/**
* @brief Returns a list of active ChatHandlers for a given accountId, peerId pair.
* @param accountId
* @param peerId
* @return Vector with active ChatHandler ids for a given accountId, peerId pair.
*/
std::vector<std::string> getChatHandlerStatus(const std::string& accountId,
const std::string& peerId);
/**
* @brief getChatHandlerDetails
* @param chatHandlerIdStr of the chat handler
* @return map of Chat Handler Details
* @brief Gets details from ChatHandler implementation.
* @param chatHandlerIdStr
* @return Details map from the ChatHandler implementation
*/
std::map<std::string, std::string> getChatHandlerDetails(const std::string& chatHandlerIdStr);
/**
* @brief Sets a preference that may be changed while ChatHandler is active.
* @param key
* @param value
* @param rootPath
* @return False if preference was changed.
*/
bool setPreference(const std::string& key,
const std::string& value,
const std::string& rootPath);
private:
/**
* @brief Exposes ChatHandlers' life cycle managers services to the main API.
* @param pluginManager
*/
void registerComponentsLifeCycleManagers(PluginManager& pluginManager);
/**
* @brief Exposes ChatHandlers services that aren't related to handlers' life cycle
* to the main API.
* @param pluginManager
*/
void registerChatService(PluginManager& pluginManager);
void toggleChatHandler(const uintptr_t chatHandlerId,
const std::string& accountId,
const std::string& peerId,
const bool toggle);
// Components that a plugin can register through registerChatHandler service.
// These objects can then be activated with toggleChatHandler.
std::list<ChatHandlerPtr> chatHandlers_;
std::map<std::pair<std::string, std::string>, std::set<uintptr_t>>
chatHandlerToggled_; // {account,peer}, list of chatHandlers
// Component that stores active ChatHandlers for each existing accountId, peerId pair.
std::map<std::pair<std::string, std::string>, std::set<uintptr_t>> chatHandlerToggled_;
// When there is a new message, chat subjects are created.
// Here we store a reference to them in order to make them interact with
// ChatHandlers.
// For easy access they are mapped accordingly to the accountId, peerId pair to
// which they belong.
std::map<std::pair<std::string, std::string>, chatSubjectPtr> chatSubjects_;
// Maps a ChatHandler name and the address of this ChatHandler.
std::map<std::string, uintptr_t> handlersNameMap_ {};
/// Component that stores persistent ChatHandlers' status for each existing
/// accountId, peerId pair.
/// A map of accountId, peerId pairs and ChatHandler-status pairs.
// Component that stores persistent ChatHandlers' status for each existing
// accountId, peerId pair.
// A map of accountId, peerId pairs and ChatHandler-status pairs.
ChatHandlerList allowDenyList_ {};
};
} // namespace jami
/*
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
......@@ -35,68 +35,85 @@
#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 */
#define JAMI_PLUGIN_ABI_VERSION 1 // 0 doesn't exist, considered as error
// JAMI_PLUGIN_API_VERSION reflects changes in Services Managers
// (CallServicesManager and ChatServicesMangers) and in JAMI_PluginAPI.
#define JAMI_PLUGIN_API_VERSION 1 // 0 doesn't exist, considered as error
C_INTERFACE_START;
/**
* @struct JAMI_PluginVersion
* @brief Contains ABI and API versions
*/
typedef struct JAMI_PluginVersion
{
/* plugin is not loadable if this number differs from one
* stored in the plugin loader */
// Plugin is not loadable if this number differs from th one
// stored in the plugin loader
uint32_t abi;
/* a difference on api number may be acceptable, see the loader code */
// A difference in API number may be acceptable, see the loader code
uint32_t api;
} JAMI_PluginVersion;
struct JAMI_PluginAPI;
/* JAMI_PluginCreateFunc parameters */
/**
* @struct JAMI_PluginObjectParams
* @brief JAMI_PluginCreateFunc parameter
*/
typedef struct JAMI_PluginObjectParams
{
const JAMI_PluginAPI* pluginApi; /* this API */
const JAMI_PluginAPI* pluginApi; // this API
const char* type;
} JAMI_PluginObjectParams;
// Function that may be implemented by plugin and called by daemon
typedef void* (*JAMI_PluginCreateFunc)(JAMI_PluginObjectParams* params, void* closure);
// Function that destroys a JAMI_PluginCreateFunc instance
typedef void (*JAMI_PluginDestroyFunc)(void* object, void* closure);
/* JAMI_PluginAPI.registerObjectFactory data */
/**
* @struct JAMI_PluginObjectFactory
* @brief This structure is filled by plugin.
* JAMI_PluginAPI.registerObjectFactory data
*/
typedef struct JAMI_PluginObjectFactory
{
JAMI_PluginVersion version;
void* closure; /* closure for create */
void* closure; // closure for create
JAMI_PluginCreateFunc create;
JAMI_PluginDestroyFunc destroy;
} JAMI_PluginObjectFactory;
/* Plugins exposed API prototype */
// 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
/**
* @struct JAMI_PluginAPI
* @brief This structure is filled by the PluginManager.
*/
typedef struct JAMI_PluginAPI
{
JAMI_PluginVersion version; /* structure version, always the first data */
void* context; /* opaque structure used by next functions */
JAMI_PluginVersion version; // Structure version, always the first data
void* context; // Opaque structure used by next functions
/* API usable by plugin implementors */
// API usable by plugin implementors
JAMI_PluginFunc registerObjectFactory;
JAMI_PluginFunc invokeService;
JAMI_PluginFunc manageComponent;
} JAMI_PluginAPI;
// Plugins destruction function prototype
typedef void (*JAMI_PluginExitFunc)(void);
// Plugins main function prototype
typedef JAMI_PluginExitFunc (*JAMI_PluginInitFunc)(const JAMI_PluginAPI* api);
C_INTERFACE_END;
#define JAMI_DYN_INIT_FUNC_NAME "JAMI_dynPluginInit"
#define JAMI_DYN_INIT_FUNC_NAME "JAMI_dynPluginInit" // Main function expected name
#define JAMI_PLUGIN_INIT_STATIC(fname, pname) JAMI_PLUGIN_INIT(fname, pname)
#define JAMI_PLUGIN_INIT_DYNAMIC(pname) JAMI_PLUGIN_INIT(JAMI_dynPluginInit, pname)
......
/**
* Copyright (C)2020-2021 Savoir-faire Linux Inc.
/*
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -19,11 +19,13 @@
*/
#include "jamipluginmanager.h"
#include "pluginsutils.h"
#include "fileutils.h"
#include "archiver.h"
#include "logger.h"
#include <sstream>
#include <fstream>
#include <regex>
#include <stdexcept>
#include <msgpack.hpp>
#include "manager.h"
......@@ -33,36 +35,6 @@ extern "C" {
#include <archive.h>
}
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#define ABI "armeabi-v7a"
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#if __ANDROID__
#define ABI "x86"
#else
#define ABI "x86-linux-gnu"
#endif
#elif defined(__x86_64__)
#if __ANDROID__
#define ABI "x86_64"
#else
#define ABI "x86_64-linux-gnu"
#endif
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#elif defined(WIN32)
#define ABI "x64-windows"
#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 */
......@@ -76,77 +48,6 @@ extern "C" {
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();
std::string iconPath = root.get("iconPath", "icon.png").asString();
if (!name.empty() || !version.empty()) {
return {{"name", name},
{"description", description},
{"version", version},
{"iconPath", iconPath}};
} 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-zA-Z0-9]+(?:[_-]?[a-zA-Z0-9]+)*)" DIR_SEPARATOR_STR_ESC
"([a-zA-Z0-9_-]+\\.(so|dll|lib).*)");
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::map<std::string, std::string>
JamiPluginManager::getPluginDetails(const std::string& rootPath)
{
......@@ -155,7 +56,8 @@ JamiPluginManager::getPluginDetails(const std::string& rootPath)
return detailsIt->second;
}
std::map<std::string, std::string> details = parseManifestFile(manifestPath(rootPath));
std::map<std::string, std::string> details = PluginUtils::parseManifestFile(
PluginUtils::manifestPath(rootPath));
if (!details.empty()) {
auto it = details.find("iconPath");
it->second.insert(0, rootPath + DIR_SEPARATOR_CH + "data" + DIR_SEPARATOR_CH);
......@@ -169,21 +71,23 @@ JamiPluginManager::getPluginDetails(const std::string& rootPath)
std::vector<std::string>
JamiPluginManager::getInstalledPlugins()
{
// Gets all plugins in standard path
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);
return !PluginUtils::checkPluginValidity(path);
};
auto returnIterator = std::remove_if(pluginsPaths.begin(), pluginsPaths.end(), predicate);
pluginsPaths.erase(returnIterator, std::end(pluginsPaths));
// Gets plugins installed in non standard path
std::vector<std::string> nonStandardInstalls = jami::Manager::instance()
.pluginPreferences.getInstalledPlugins();
for (auto& path : nonStandardInstalls) {
if (checkPluginValidity(path))
if (PluginUtils::checkPluginValidity(path))
pluginsPaths.emplace_back(path);
}
......@@ -196,7 +100,7 @@ JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
int r {0};
if (fileutils::isFile(jplPath)) {
try {
auto manifestMap = readPluginManifestFromArchive(jplPath);
auto manifestMap = PluginUtils::readPluginManifestFromArchive(jplPath);
std::string name = manifestMap["name"];
if (name.empty())
return 0;
......@@ -204,13 +108,16 @@ JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
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));
const auto alreadyInstalledManifestMap = PluginUtils::parseManifestFile(
PluginUtils::manifestPath(destinationDir));
if (!alreadyInstalledManifestMap.empty()) {
if (force) {
r = uninstallPlugin(destinationDir);
if (r == 0) {
archiver::uncompressArchive(jplPath, destinationDir, uncompressJplFunction);
archiver::uncompressArchive(jplPath,
destinationDir,
PluginUtils::uncompressJplFunction);
}
} else {
std::string installedVersion = alreadyInstalledManifestMap.at("version");
......@@ -219,18 +126,18 @@ JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
if (r == 0) {
archiver::uncompressArchive(jplPath,
destinationDir,
uncompressJplFunction);
PluginUtils::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);
archiver::uncompressArchive(jplPath,
destinationDir,
PluginUtils::uncompressJplFunction);
}
loadPlugin(destinationDir);
} catch (const std::exception& e) {
......@@ -243,7 +150,7 @@ JamiPluginManager::installPlugin(const std::string& jplPath, bool force)
int
JamiPluginManager::uninstallPlugin(const std::string& rootPath)
{
if (checkPluginValidity(rootPath)) {
if (PluginUtils::checkPluginValidity(rootPath)) {
auto detailsIt = pluginDetailsMap_.find(rootPath);
if (detailsIt != pluginDetailsMap_.end()) {
bool loaded = pm_.checkLoadedPlugin(rootPath);
......@@ -308,7 +215,9 @@ JamiPluginManager::getLoadedPlugins() const
std::transform(loadedSoPlugins.begin(),
loadedSoPlugins.end(),
std::back_inserter(loadedPlugins),
[this](const std::string& soPath) { return getRootPathFromSoPath(soPath); });
[this](const std::string& soPath) {
return PluginUtils::getRootPathFromSoPath(soPath);
});
return loadedPlugins;
}
......@@ -318,12 +227,6 @@ JamiPluginManager::getPluginPreferences(const std::string& rootPath)
return PluginPreferencesUtils::getPreferences(rootPath);
}
std::map<std::string, std::string>
JamiPluginManager::getPluginUserPreferencesValuesMap(const std::string& rootPath)
{
return PluginPreferencesUtils::getUserPreferencesValuesMap(rootPath);
}
bool
JamiPluginManager::setPluginPreference(const std::string& rootPath,
const std::string& key,
......@@ -336,8 +239,10 @@ JamiPluginManager::setPluginPreference(const std::string& rootPath,
std::vector<std::map<std::string, std::string>> preferences
= PluginPreferencesUtils::getPreferences(rootPath);
// If any plugin handler is active we may have to reload it
bool force {pm_.checkLoadedPlugin(rootPath)};
// We check if the preference is modified without having to reload plugin
for (auto& preference : preferences) {
if (!preference["key"].compare(key)) {
force &= callsm_.setPreference(key, value, rootPath);
......@@ -348,6 +253,7 @@ JamiPluginManager::setPluginPreference(const std::string& rootPath,
if (force)
unloadPlugin(rootPath);
// Save preferences.msgpack with modified preferences values
auto find = pluginPreferencesMap.find(key);
if (find != pluginPreferencesMap.end()) {
pluginUserPreferencesMap[key] = value;
......@@ -396,47 +302,22 @@ JamiPluginManager::resetPluginPreferencesValuesMap(const std::string& rootPath)
return status;
}
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();
}
return {};
}
std::map<std::string, std::string>
JamiPluginManager::parseManifestFile(const std::string& manifestFilePath)
{
std::lock_guard<std::mutex> guard(fileutils::getFileLock(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
// Register getPluginPreferences so that plugin's can receive it's preferences
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()));
*ppp = PluginPreferencesUtils::getPreferencesValuesMap(
PluginUtils::getRootPathFromSoPath(plugin->getPath()));
return 0;
});
// Register getPluginDataPath so that plugin's can receive the path to it's data folder
pm_.registerService("getPluginDataPath", [this](const DLPlugin* plugin, void* data) {
auto dataPath_ = static_cast<std::string*>(data);
dataPath_->assign(dataPath(plugin->getPath()));
dataPath_->assign(PluginUtils::dataPath(plugin->getPath()));
return 0;
});
}
} // namespace jami
/**
* Copyright (C)2020-2021 Savoir-faire Linux Inc.
/*
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -19,23 +19,26 @@
*/
#pragma once
#include "noncopyable.h"
#include "fileutils.h"
#include "archiver.h"
#include "pluginmanager.h"
#include "pluginpreferencesutils.h"
// Services
#include "callservicesmanager.h"
#include "chatservicesmanager.h"
#include <vector>
#include <map>
#include <list>
#include <string>
#include <algorithm>
namespace jami {
/**
* @class JamiPluginManager
* @brief This class provides an interface to functions exposed to the
* Plugin System interface for lrc and clients.
*/
class JamiPluginManager
{
public:
......@@ -45,35 +48,26 @@ public:
{
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"}
* @brief Parses a manifest file and return its content
* along with other internally added values.
* @param rootPath installation path
* @return Map where the keyset is {"name", "description", "version", "iconPath", "soPath"}
*/
std::map<std::string, std::string> getPluginDetails(const std::string& rootPath);
/**
* @brief getInstalledPlugins
* Lists available plugins with valid manifest files
* @return list of plugin directory names
* @brief Returns a vector with installed plugins
*/
std::vector<std::string> getInstalledPlugins();
/**
* @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
* @brief Checks if the plugin has a valid manifest, installs the plugin if not
* previously installed or if installing a newer version of it.
* @param jplPath
* @param force
* @return + 0 if success
* @param force If true, allows installing an older plugin version.
* @return 0 if success
* 100 if already installed with similar version
* 200 if already installed with newer version
* libarchive error codes otherwise
......@@ -81,41 +75,60 @@ public:
int installPlugin(const std::string& jplPath, bool force);
/**
* @brief uninstallPlugin
* Checks if the plugin has a valid manifest then removes plugin folder
* @brief Checks if the plugin has a valid manifest and if the plugin is loaded,
* tries to unload it and then removes plugin folder.
* @param rootPath
* @return 0 if success
*/
int uninstallPlugin(const std::string& rootPath);
/**
* @brief loadPlugin
* @brief Returns True if success
* @param rootPath of the plugin folder
* @return true is success
*/
bool loadPlugin(const std::string& rootPath);
/**
* @brief unloadPlugin
* @brief Returns True if success
* @param rootPath of the plugin folder
* @return true is success
*/
bool unloadPlugin(const std::string& rootPath);
/**
* @brief getLoadedPlugins
* @return vector of rootpaths of the loaded plugins
* @brief Returns vector with rootpaths of the loaded plugins
*/
std::vector<std::string> getLoadedPlugins() const;
/**
* @brief Returns contents of plugin's preferences.json file
* @param rootPath
*/
std::vector<std::map<std::string, std::string>> getPluginPreferences(const std::string& rootPath);
/**
* @brief Returns a Map with preferences keys and values.
* @param rootPath
*/
std::map<std::string, std::string> getPluginPreferencesValuesMap(const std::string& rootPath);
/**
* @brief Modifies a preference value by saving it to a preferences.msgpack.
* Plugin is reloaded only if the preference cannot take effect immediately.
* In other words, if we have to reload plugin so that preference may take effect.
* @param rootPath
* @param key
* @param value
* @return True if success
*/
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);
/**
* @brief Reset plugin's preferences values to their defaultValues
* @param rootPath
* @return True if success.
*/
bool resetPluginPreferencesValuesMap(const std::string& rootPath);
CallServicesManager& getCallServicesManager() { return callsm_; }
......@@ -126,83 +139,17 @@ private:
NON_COPYABLE(JamiPluginManager);
/**
* @brief checkPluginValidity
* Checks if the plugin has a manifest file with a name and a version
* @return true if valid
* @brief Register services that can be called from plugin implementation side.
*/
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";
}
std::map<std::string, std::string> getPluginUserPreferencesValuesMap(const std::string& rootPath);
/**
* @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 PluginPreferencesUtils::getPreferencesConfigFilePath(rootPath);
}
/**
* @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 PluginPreferencesUtils::valuesFilePath(rootPath);
}
void registerServices();
// PluginManager instance
PluginManager pm_;
// Map between plugins installation path and manifest infos.
std::map<std::string, std::map<std::string, std::string>> pluginDetailsMap_;
// Services
// Services instances
CallServicesManager callsm_;
ChatServicesManager chatsm_;
};
......
/*
* Copyright (C) 2020 Savoir-faire Linux Inc.
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -32,36 +32,75 @@ namespace jami {
using avSubjectPtr = std::shared_ptr<Observable<AVFrame*>>;
/**
* @brief The MediaHandler class
* Is the main object of the plugin
* @class MediaHandler
* @brief It's the base object of the CallMediaHandler
*/
class MediaHandler
{
public:
virtual ~MediaHandler() = default;
/**
* @brief id
* The id is the path of the plugin that created this MediaHandler
* @return
* @brief Returns the dataPath of the plugin that created this MediaHandler.
*/
std::string id() const { return id_; }
/**
* @brief Should be called by the MediaHandler creator to set the plugins id_ variable
* with dataPath.
*/
virtual void setId(const std::string& id) final { id_ = id; }
private:
// Must be set with plugin's dataPath.
std::string id_;
};
/**
* @brief The CallMediaHandler class
* It can hold multiple streams of data, and do processing on them
* @class CallMediaHandler
* @brief This abstract class is an API we need to implement from plugin side.
* In other words, a plugin functionality that plays with audio or video, must start
* from the implementation of this class.
*/
class CallMediaHandler : public MediaHandler
{
public:
/**
* @brief Should attach a AVSubject (Observable) to the plugin data process (Observer).
* @param data
* @param subject
*/
virtual void notifyAVFrameSubject(const StreamData& data, avSubjectPtr subject) = 0;
/**
* @brief Should return a map with handler's name, iconPath, pluginId, attached, and dataType.
* Daemon expects:
* "attached" -> 1 if handler is attached;
* "dataType" -> 1 if data processed is video;
* "dataType" -> 0 if data processed is audio;
* @return Map with CallMediaHandler details.
*/
virtual std::map<std::string, std::string> getCallMediaHandlerDetails() = 0;
/**
* @brief Should detach the plugin data process (Observer).
*/
virtual void detach() = 0;
/**
* @brief If a preference can be changed without the need to reload the plugin, it
* should be done through this function.
* @param key
* @param value
*/
virtual void setPreferenceAttribute(const std::string& key, const std::string& value) = 0;
/**
* @brief If a preference can be changed without the need to reload the plugin, this function
* should return True.
* @param key
* @return True if preference can be changed through setPreferenceAttribute method.
*/
virtual bool preferenceMapHasKey(const std::string& key) = 0;
};
} // namespace jami
/*
* Copyright (C) 2004-2018 Savoir-faire Linux Inc.
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
......@@ -46,5 +46,4 @@ Plugin::load(const std::string& path, std::string& error)
return new DLPlugin(handle, path);
}
} // namespace jami
......@@ -27,13 +27,26 @@
namespace jami {
/**
* @class Plugin
* @brief This class is used to attempt loading a plugin library.
*/
class Plugin
{
public:
virtual ~Plugin() = default;
/**
* @brief Load plugin's library.
* @return DLPlugin if success.
*/
static Plugin* load(const std::string& path, std::string& error);
virtual void* getSymbol(const char* name) const = 0;
/**
* @brief Search loaded library for its initialization function
* @return Plugin's initialization function.
*/
virtual JAMI_PluginInitFunc getInitFunction() const
{
return reinterpret_cast<JAMI_PluginInitFunc>(getSymbol(JAMI_DYN_INIT_FUNC_NAME));
......@@ -43,6 +56,10 @@ protected:
Plugin() = default;
};
/**
* @class DLPlugin
* @brief This class is used after a plugin library is successfully loaded.
*/
class DLPlugin : public Plugin
{
public:
......@@ -54,6 +71,11 @@ public:
}
virtual ~DLPlugin() { unload(); }
/**
* @brief Unload plugin's library.
* @return True if success.
*/
bool unload()
{
if (!handle_) {
......@@ -62,6 +84,11 @@ public:
return !(::dlclose(handle_.release()));
}
/**
* @brief Searchs for symbol in library.
* @param name
* @return symbol.
*/
void* getSymbol(const char* name) const
{
if (!handle_)
......@@ -77,7 +104,9 @@ public:
JAMI_PluginAPI api_;
private:
// Pointer to the loaded library returned by dlopen
std::unique_ptr<void, int (*)(void*)> handle_;
// Plugin's data path
const std::string path_;
};
......
......@@ -56,21 +56,25 @@ PluginManager::load(const std::string& path)
}
std::string error;
// Load plugin library
std::unique_ptr<Plugin> plugin(Plugin::load(path, error));
if (!plugin) {
JAMI_ERR() << "Plugin: " << error;
return false;
}
// Get init function from loaded library
const auto& init_func = plugin->getInitFunction();
if (!init_func) {
JAMI_ERR() << "Plugin: no init symbol" << error;
return false;
}
// Register plugin by running init function
if (!registerPlugin(plugin))
return false;
// Put Plugin loader into loaded plugins Map.
dynPluginMap_[path] = {std::move(plugin), true};
return true;
}
......@@ -79,7 +83,7 @@ bool
PluginManager::unload(const std::string& path)
{
destroyPluginComponents(path);
PluginMap::iterator it = dynPluginMap_.find(path);
auto it = dynPluginMap_.find(path);
if (it != dynPluginMap_.end()) {
it->second.second = false;
}
......@@ -127,10 +131,9 @@ bool
PluginManager::callPluginInitFunction(const std::string& path)
{
bool returnValue {false};
PluginMap::iterator it = dynPluginMap_.find(path);
auto 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
// Since the Plugin was found it's of type DLPlugin with a valid init symbol
std::shared_ptr<DLPlugin> plugin = std::static_pointer_cast<DLPlugin>(it->second.first);
const auto& initFunc = plugin->getInitFunction();
JAMI_PluginExitFunc exitFunc = nullptr;
......@@ -157,19 +160,17 @@ PluginManager::callPluginInitFunction(const std::string& path)
bool
PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin)
{
// Here we know that Plugin is of type DLPlugin with a valid init symbol
// Here we already 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());
JAMI_PluginExitFunc exitFunc = nullptr;
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.
*/
// 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_);
......@@ -181,10 +182,8 @@ PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin)
return manager->invokeService(plugin, name, data);
};
/**
* Implements JAMI_PluginAPI.invokeService().
* Must be C accessible.
*/
// Implements JAMI_PluginAPI.manageComponents().
// Must be C accessible.
pluginPtr->api_.manageComponent = [](const JAMI_PluginAPI* api, const char* name, void* data) {
auto plugin = static_cast<DLPlugin*>(api->context);
if (!plugin) {
......@@ -206,15 +205,11 @@ PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin)
}
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;
}
......@@ -234,6 +229,7 @@ PluginManager::unRegisterService(const std::string& name)
int32_t
PluginManager::invokeService(const DLPlugin* plugin, const std::string& name, void* data)
{
// Search if desired service exists
const auto& iterFunc = services_.find(name);
if (iterFunc == services_.cend()) {
JAMI_ERR() << "Services not found: " << name;
......@@ -243,6 +239,7 @@ PluginManager::invokeService(const DLPlugin* plugin, const std::string& name, vo
const auto& func = iterFunc->second;
try {
// Call service with data
return func(plugin, data);
} catch (const std::runtime_error& e) {
JAMI_ERR() << e.what();
......@@ -273,7 +270,6 @@ PluginManager::manageComponent(const DLPlugin* plugin, const std::string& name,
}
}
/* WARNING: exposed to plugins through JAMI_PluginAPI */
bool
PluginManager::registerObjectFactory(const char* type, const JAMI_PluginObjectFactory& factoryData)
{
......@@ -297,13 +293,13 @@ PluginManager::registerObjectFactory(const char* type, const JAMI_PluginObjectFa
};
ObjectFactory factory = {factoryData, deleter};
// wildcard registration?
// Wildcard registration
if (key == "*") {
wildCardVec_.push_back(factory);
return true;
}
// fails on duplicate for exactMatch map
// Fails on duplicate for exactMatch map
if (exactMatchMap_.find(key) != exactMatchMap_.end())
return false;
......@@ -345,7 +341,7 @@ PluginManager::createObject(const std::string& type)
for (const auto& factory : wildCardVec_) {
auto object = factory.data.create(&op, factory.data.closure);
if (object) {
// promote registration to exactMatch_
// Promote registration to exactMatch_
// (but keep also wildcard registration for other object types)
int32_t res = registerObjectFactory(op.type, factory.data);
if (res < 0) {
......@@ -360,7 +356,6 @@ PluginManager::createObject(const std::string& type)
return {nullptr, nullptr};
}
/* WARNING: exposed to plugins through JAMI_PluginAPI */
int32_t
PluginManager::registerObjectFactory_(const JAMI_PluginAPI* api, const char* type, void* data)
{
......
......@@ -26,9 +26,7 @@
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <list>
......@@ -36,140 +34,174 @@
namespace jami {
class Plugin;
/**
* @class PluginManager
* @brief This class manages plugin (un)loading. Those process include:
* (1) plugin libraries (un)loading;
* (2) call plugin initial function;
* (3) handlers registration and destruction, and;
* (4) services registration.
*/
class PluginManager
{
public:
PluginManager();
~PluginManager();
private:
using ObjectDeleter = std::function<void(void*)>;
// Function that may be called from plugin implementation
using ServiceFunction = std::function<int32_t(const DLPlugin*, void*)>;
// A Component is either a MediaHandler or a ChatHandler.
// A ComponentFunction is a function that may start or end a component life.
using ComponentFunction = std::function<int32_t(void*)>;
// A vector to a pair<componentType, componentPtr>
using ComponentTypePtrVector = std::list<std::pair<std::string, void*>>;
private:
// A list of component type (MediaHandler or ChatHandler), and component pointer pairs
using ComponentPtrList = std::list<std::pair<std::string, void*>>;
struct ObjectFactory
{
JAMI_PluginObjectFactory data;
ObjectDeleter deleter;
};
/**
* @struct ComponentLifeCycleManager
* @brief Component functions for registration and destruction.
*/
struct ComponentLifeCycleManager
{
// Register component to servicesmanager
ComponentFunction takeComponentOwnership;
// Destroys component in servicesmanager
ComponentFunction destroyComponent;
};
// Map between plugin's library path and loader pointer
using PluginMap = std::map<std::string, std::pair<std::shared_ptr<Plugin>, bool>>;
using PluginComponentsMap = std::map<std::string, ComponentTypePtrVector>;
// Map between plugins' library path and their components list
using PluginComponentsMap = std::map<std::string, ComponentPtrList>;
// Vector with plugins' destruction functions
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.
*
* @brief Load a dynamic plugin by filename.
* @param path fully qualified pathname on a loadable plugin binary
* @return true if success
* @return True if success
*/
bool load(const std::string& path);
/**
* @brief unloads the plugin with pathname path
* @brief Unloads the plugin
* @param path
* @return true if success
* @return True if success
*/
bool unload(const std::string& path);
/**
* @brief getLoadedPlugins
* @return vector of strings of so files of the loaded plugins
* @brief Returns vector with loaded plugins' libraries paths
*/
std::vector<std::string> getLoadedPlugins() const;
/**
* @brief checkLoadedPlugin
* @return bool True if plugin is loaded, false otherwise
* @brief Returns True if plugin is loaded
*/
bool checkLoadedPlugin(const std::string& rootPath) 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
* @brief Register a new service in the Plugin System.
* @param name The service name
* @param func The function that may be called by Ring_PluginAPI.invokeService
* @return True if success
*/
bool registerPlugin(std::unique_ptr<Plugin>& plugin);
bool registerService(const std::string& name, ServiceFunction&& func);
/**
* Register a new service for plugin.
*
* @brief Unregister a service from the Plugin System.
* @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
* @brief Function called from plugin implementation register a new object factory.
*
* Note: type can be the string "*" meaning that the factory
* will be called if no exact match factories are found for a given type.
* @param type unique identifier of the object
* @param params object factory details
* @return True if success
*/
bool registerObjectFactory(const char* type, const JAMI_PluginObjectFactory& factory);
/**
* @brief registerComponentManager
* Registers a component manager that will have two functions, one to take
* @brief 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 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
* @param destroyComponent destroy the component
* @return True if success
*/
bool registerComponentManager(const std::string& name,
ComponentFunction&& takeOwnership,
ComponentFunction&& destroyComponent);
private:
NON_COPYABLE(PluginManager);
/**
* Create a new plugin's exported object.
*
* @brief Untoggle and destroys all plugin's handlers from handlerservices
* @param path
*/
void destroyPluginComponents(const std::string& path);
/**
* @brief Returns True if success
* @param path plugin path used as an id in the plugin map
*/
bool callPluginInitFunction(const std::string& path);
/**
* @brief Returns True if success
* @param initFunc plugin init function
*/
bool registerPlugin(std::unique_ptr<Plugin>& plugin);
/**
* @brief Creates a new plugin's exported object.
* @param type unique identifier of the object to create.
* @return unique pointer on created object.
* @return Unique pointer on created object.
*/
std::unique_ptr<void, ObjectDeleter> createObject(const std::string& type);
private:
NON_COPYABLE(PluginManager);
/**
* Implements JAMI_PluginAPI.registerObjectFactory().
* WARNING: exposed to plugins through JAMI_PluginAPI
* @brief Implements JAMI_PluginAPI.registerObjectFactory().
* Must be C accessible.
* @param api
* @param type
* @param data
*/
static int32_t registerObjectFactory_(const JAMI_PluginAPI* api, const char* type, void* data);
/** WARNING: exposed to plugins through JAMI_PluginAPI
* @brief Function called from plugin implementation to perform a service.
* @param name The service name
*/
int32_t invokeService(const DLPlugin* plugin, const std::string& name, void* data);
/**
* WARNING: exposed to plugins through JAMI_PluginAPI
* @brief Function called from plugin implementation to manage a component.
* @param name The component type
*/
int32_t manageComponent(const DLPlugin* plugin, const std::string& name, void* data);
std::mutex mutex_ {};
......@@ -178,22 +210,23 @@ private:
registerObjectFactory_,
nullptr,
nullptr};
PluginMap dynPluginMap_ {}; // Only dynamic loaded plugins
// Keeps a map between plugin library path and a Plugin instance
// for dynamically loaded plugins.
PluginMap dynPluginMap_ {};
// Should keep reference to plugins' destruction functions read during library loading.
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
// Keeps a map between services names and service functions.
std::map<std::string, ServiceFunction> services_ {};
// registered component lifecycle managers
// Keeps a ComponentsLifeCycleManager for each available Handler API.
std::map<std::string, ComponentLifeCycleManager> componentsLifeCycleManagers_ {};
// references to plugins components, used for cleanup
// Keeps a map between plugins' library path and their components list.
PluginComponentsMap pluginComponentsMap_ {};
};
} // namespace jami
/*
* Copyright (C) 2020 Savoir-faire Linux Inc.
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -101,19 +101,26 @@ PluginPreferencesUtils::getPreferences(const std::string& rootPath)
std::set<std::string> keys;
std::vector<std::map<std::string, std::string>> preferences;
if (file) {
// 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;
}
......@@ -174,11 +181,13 @@ PluginPreferencesUtils::getPreferencesValuesMap(const std::string& rootPath)
{
std::map<std::string, std::string> rmap;
// Read all preferences values
std::vector<std::map<std::string, std::string>> preferences = getPreferences(rootPath);
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;
}
......@@ -275,15 +284,17 @@ PluginPreferencesUtils::addAlwaysHandlerPreference(const std::string& handlerNam
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 immediatly";
preference["summary"] = handlerName + " will take effect immediately";
root.append(preference);
file.close();
}
......@@ -291,6 +302,7 @@ PluginPreferencesUtils::addAlwaysHandlerPreference(const std::string& handlerNam
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();
}
......
/*
* Copyright (C) 2020 Savoir-faire Linux Inc.
* Copyright (C) 2020-2021 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
......@@ -29,37 +29,115 @@ namespace jami {
using ChatHandlerList = std::map<std::pair<std::string, std::string>, std::map<std::string, bool>>;
/**
* @class PluginPreferencesUtils
* @brief Static class that gathers functions to manage
* plugins' preferences.
*/
class PluginPreferencesUtils
{
public:
/**
* @brief Given a plugin installation path, returns the path to the
* preference.json of this plugin.
* @param rootPath
* @return preference.json file path.
*/
static std::string getPreferencesConfigFilePath(const std::string& rootPath);
/**
* @brief Given a plugin installation path, returns the path to the
* preference.msgpack file.
* The preference.msgpack file saves the actuall preferences values
* if they were modified.
* @param rootPath
* @return preference.msgpack file path.
*/
static std::string valuesFilePath(const std::string& rootPath);
/**
* @brief Returns the path to allowdeny.msgpack file.
* The allowdeny.msgpack file persists ChatHandlers status for each
* conversation this handler was previously (de)activated.
* @return allowdeny.msgpack file path.
*/
static std::string getAllowDenyListsPath();
/**
* @brief Returns a colon separated string with values from a json::Value containing an array.
* @param jsonArray
* @return Colon separated string with jsonArray contents.
*/
static std::string convertArrayToString(const Json::Value& jsonArray);
/**
* @brief Parses a single preference from json::Value to a Map<string, string>.
* @param jsonPreference
* @return std::map<std::string, std::string> preference
*/
static std::map<std::string, std::string> parsePreferenceConfig(
const Json::Value& jsonPreference);
/**
* @brief Reads a preference.json file from the plugin installed in rootPath.
* @param rootPath
* @return std::vector<std::map<std::string, std::string>> with preferences.json content
*/
static std::vector<std::map<std::string, std::string>> getPreferences(
const std::string& rootPath);
/**
* @brief Reads preferences values which were modified from defaultValue
* @param rootPath
* @return Map with preference keys and actuall values.
*/
static std::map<std::string, std::string> getUserPreferencesValuesMap(
const std::string& rootPath);
/**
* @brief Reads preferences values
* @param rootPath
* @return Map with preference keys and actuall values.
*/
static std::map<std::string, std::string> getPreferencesValuesMap(const std::string& rootPath);
/**
* @brief Resets all preferences values to their defaultValues
* by erasing all data saved in preferences.msgpack.
* @param rootPath
* @return True if preferences were reset.
*/
static bool resetPreferencesValuesMap(const std::string& rootPath);
static std::string getAllowDenyListsPath();
/**
* @brief Saves ChantHandlers status provided by list.
* @param [in] list
*/
static void setAllowDenyListPreferences(const ChatHandlerList& list);
/**
* @brief Reads ChantHandlers status from allowdeny.msgpack file.
* @param [out] list
*/
static void getAllowDenyListPreferences(ChatHandlerList& list);
/**
* @brief Creates a "always" preference for a handler if this preference doesn't exist yet.
* A "always" preference tells the Plugin System if in the event of a new call or chat message,
* the handler is suposed to be automatically activated.
* @param handlerName
* @param rootPath
*/
static void addAlwaysHandlerPreference(const std::string& handlerName,
const std::string& rootPath);
/**
* @brief Read plugin's preferences and returns wheter a specific handler
* "always" preference is True or False.
* @param rootPath
* @param handlerName
* @return True if the handler should be automatically toggled
*/
static bool getAlwaysPreference(const std::string& rootPath, std::string& handlerName);
private:
......
/*
* Copyright (C) 2021 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 "pluginsutils.h"
#include "logger.h"
#include "fileutils.h"
#include "archiver.h"
#include <fstream>
#include <regex>
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#define ABI "armeabi-v7a"
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#if __ANDROID__
#define ABI "x86"
#else
#define ABI "x86-linux-gnu"
#endif
#elif defined(__x86_64__)
#if __ANDROID__
#define ABI "x86_64"
#else
#define ABI "x86_64-linux-gnu"
#endif
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#elif defined(WIN32)
#define ABI "x64-windows"
#else
#define ABI "unknown"
#endif
namespace jami {
namespace PluginUtils {
// DATA_REGEX is used to during the plugin jpl uncompressing
const std::regex DATA_REGEX("^data" DIR_SEPARATOR_STR_ESC ".+");
// SO_REGEX is used to find libraries during the plugin jpl uncompressing
const std::regex SO_REGEX("([a-zA-Z0-9]+(?:[_-]?[a-zA-Z0-9]+)*)" DIR_SEPARATOR_STR_ESC
"([a-zA-Z0-9_-]+\\.(so|dll|lib).*)");
std::string
manifestPath(const std::string& rootPath)
{
return rootPath + DIR_SEPARATOR_CH + "manifest.json";
}
std::string
getRootPathFromSoPath(const std::string& soPath)
{
return soPath.substr(0, soPath.find_last_of(DIR_SEPARATOR_CH));
}
std::string
dataPath(const std::string& pluginSoPath)
{
return getRootPathFromSoPath(pluginSoPath) + DIR_SEPARATOR_CH + "data";
}
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();
std::string iconPath = root.get("iconPath", "icon.png").asString();
if (!name.empty() || !version.empty()) {
return {{"name", name},
{"description", description},
{"version", version},
{"iconPath", iconPath}};
} 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;
if (Json::parseFromStream(rbuilder, stream, &root, &errs)) {
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");
}
}
std::map<std::string, std::string>
parseManifestFile(const std::string& manifestFilePath)
{
std::lock_guard<std::mutex> guard(fileutils::getFileLock(manifestFilePath));
std::ifstream file(manifestFilePath);
if (file) {
try {
return checkManifestValidity(file);
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
}
return {};
}
bool
checkPluginValidity(const std::string& rootPath)
{
return !parseManifestFile(manifestPath(rootPath)).empty();
}
std::map<std::string, std::string>
readPluginManifestFromArchive(const std::string& jplPath)
{
try {
return checkManifestValidity(archiver::readFileFromArchive(jplPath, "manifest.json"));
} catch (const std::exception& e) {
JAMI_ERR() << e.what();
}
return {};
}
std::pair<bool, const std::string>
uncompressJplFunction(const std::string& relativeFileName)
{
std::smatch match;
// manifest.json and files under data/ folder remains in the same structure
// but libraries files are extracted from the folder that matches the running ABI to
// the main installation path.
if (relativeFileName == "manifest.json" || std::regex_match(relativeFileName, DATA_REGEX)) {
return std::make_pair(true, relativeFileName);
} else if (regex_search(relativeFileName, match, SO_REGEX)) {
if (match.str(1) == ABI) {
return std::make_pair(true, match.str(2));
}
}
return std::make_pair(false, std::string {""});
}
} // namespace PluginUtils
} // namespace jami
/*
* Copyright (C) 2021 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.
*/
#pragma once
#include <map>
#include <vector>
#include <json/json.h>
namespace jami {
/**
* @namespace PluginUtils
* @brief This namespace provides auxiliary functions to the Plugin System.
* Specially to the JamiPluginManager class.
* Those functions were originally part of the latter class, but for
* code clarity purposes, they were moved.
*/
namespace PluginUtils {
/**
* @brief Returns complete manifest.json file path given a installation path.
* @param rootPath
*/
std::string manifestPath(const std::string& rootPath);
/**
* @brief Returns installation path given a plugin's library path.
* @param soPath
*/
std::string getRootPathFromSoPath(const std::string& soPath);
/**
* @brief Returns data path given a plugin's library path.
* @param pluginSoPath
*/
std::string dataPath(const std::string& pluginSoPath);
/**
* @brief Check if manifest.json has minimum format and parses its content
* to a map<string, string>.
* @param root
* @return Maps with manifest.json content if success.
*/
std::map<std::string, std::string> checkManifestJsonContentValidity(const Json::Value& root);
/**
* @brief Reads manifest.json stream and checks if it's valid.
* @param stream
* @return Maps with manifest.json content if success.
*/
std::map<std::string, std::string> checkManifestValidity(std::istream& stream);
/**
* @brief Recives manifest.json file contents, and checks its validity.
* @param vec
* @return Maps with manifest.json content if success.
*/
std::map<std::string, std::string> checkManifestValidity(const std::vector<uint8_t>& vec);
/**
* @brief Parses the manifest file of an installed plugin if it's valid.
* @param manifestFilePath
* @return Map with manifest contents
*/
std::map<std::string, std::string> parseManifestFile(const std::string& manifestFilePath);
/**
* @brief Validates a plugin based on its manifest.json file.
* @param rootPath
* @return True if valid
*/
bool checkPluginValidity(const std::string& rootPath);
/**
* @brief Reads the manifest file content without uncompressing the whole archive and
* return a map with manifest contents if success.
* @param jplPath
* @return Map with manifest contents
*/
std::map<std::string, std::string> readPluginManifestFromArchive(const std::string& jplPath);
/**
* @brief Function used by archiver to extract files from plugin jpl to the plugin
* installation path.
* @param relativeFileName
* @return Pair <bool, string> meaning if file should be extracted and where to.
*/
std::pair<bool, const std::string> uncompressJplFunction(const std::string& relativeFileName);
} // namespace PluginUtils
} // namespace jami
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment