mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
agent: Use Jami's scheme bindings
The agent will be written in Guile Scheme instead of C++ in order to give maximum flexibility to developers of scenarios. Thus, bindings will be added for the public Jami interface. In other words, the agent will be a client of yet another Jami's bindings. Change-Id: Ic2cd333007d0b1aad56c02b116ea708b56e96cc5
This commit is contained in:

committed by
Adrien Béraud

parent
bfa324bcb1
commit
aa3299b36b
@ -1,15 +1,14 @@
|
||||
include $(top_srcdir)/globals.mk
|
||||
|
||||
AM_CXXFLAGS += -I$(top_srcdir)/src -I.. $(GUILE_CFLAGS) -rdynamic
|
||||
AM_CXXFLAGS += -I$(top_srcdir)/src -I./src $(GUILE_CFLAGS) -rdynamic
|
||||
AM_LDFLAGS += $(GUILE_LIBS)
|
||||
|
||||
noinst_PROGRAMS = agent
|
||||
|
||||
agent_SOURCES = \
|
||||
agent.cpp \
|
||||
agent.h \
|
||||
bindings.cpp \
|
||||
bindings.h \
|
||||
main.cpp
|
||||
agent_SOURCES = \
|
||||
src/main.cpp \
|
||||
src/utils.h \
|
||||
src/bindings/bindings.cpp \
|
||||
src/bindings/bindings.h
|
||||
|
||||
agent_LDADD = $(top_builddir)/src/libring.la
|
||||
|
@ -1,563 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Olivier Dion <olivier.dion@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.
|
||||
*/
|
||||
|
||||
/* std */
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
/* DRing */
|
||||
#include "account_const.h"
|
||||
#include "jami/presencemanager_interface.h"
|
||||
#include "jami/callmanager_interface.h"
|
||||
#include "jami/configurationmanager_interface.h"
|
||||
#include "jami/conversation_interface.h"
|
||||
|
||||
/* agent */
|
||||
#include "agent/agent.h"
|
||||
|
||||
using usize = size_t;
|
||||
|
||||
#define LOG_AGENT_STATE() AGENT_DBG("In state %s", __FUNCTION__)
|
||||
|
||||
void
|
||||
Agent::searchForPeers(std::vector<std::string>& peers)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
for (auto it = peers.begin(); it != peers.end(); ++it) {
|
||||
AGENT_INFO("Searching for peer %s", it->c_str());
|
||||
|
||||
DRing::sendTrustRequest(accountID_, it->c_str());
|
||||
DRing::subscribeBuddy(accountID_, it->c_str(), true);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Agent::ping(const std::string& conversation)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> pongReceived(false);
|
||||
|
||||
std::string alphabet = "0123456789ABCDEF";
|
||||
std::string messageSent;
|
||||
|
||||
for (usize i = 0; i < 16; ++i) {
|
||||
messageSent.push_back(alphabet[rand() % alphabet.size()]);
|
||||
}
|
||||
|
||||
onMessageReceived_.add([&](const std::string& /* accountID */,
|
||||
const std::string& conversationID,
|
||||
std::map<std::string, std::string> message) {
|
||||
if ("text/plain" != message.at("type")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto msg = message.at("body");
|
||||
|
||||
if (pongReceived.load()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (conversationID == conversation and message.at("author") != peerID_
|
||||
and msg == "PONG:" + messageSent) {
|
||||
std::unique_lock lk(mtx);
|
||||
pongReceived.store(true);
|
||||
cv.notify_one();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
AGENT_INFO("Sending ping `%s` to `%s`", messageSent.c_str(), conversation.c_str());
|
||||
|
||||
DRing::sendMessage(accountID_, conversation, messageSent, "");
|
||||
|
||||
/* Waiting for echo */
|
||||
|
||||
std::unique_lock<std::mutex> lk(mtx);
|
||||
|
||||
bool ret = (std::cv_status::no_timeout == cv.wait_for(lk, std::chrono::seconds(30))
|
||||
and pongReceived.load());
|
||||
|
||||
AGENT_INFO("Pong %s", ret ? "received" : "missing");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string
|
||||
Agent::someContact() const
|
||||
{
|
||||
auto members = DRing::getConversationMembers(accountID_, someConversation());
|
||||
|
||||
for (const auto& member : members) {
|
||||
if (member.at("uri") != peerID_) {
|
||||
return member.at("uri");
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string
|
||||
Agent::someConversation() const
|
||||
{
|
||||
if (conversations_.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
auto it = conversations_.begin();
|
||||
|
||||
std::advance(it, rand() % conversations_.size());
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
bool
|
||||
Agent::placeCall(const std::string& contact)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool success(false);
|
||||
bool over(false);
|
||||
|
||||
std::string callID = "";
|
||||
|
||||
onCallStateChanged_.add([&](const std::string& call_id, const std::string& state, signed code) {
|
||||
AGENT_INFO("[call:%s] In state %s : %d", callID.c_str(), state.c_str(), code);
|
||||
|
||||
std::unique_lock lk(mtx);
|
||||
|
||||
if (call_id != callID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
|
||||
if ("CURRENT" == state) {
|
||||
success = true;
|
||||
} else if ("OVER" == state) {
|
||||
over = true;
|
||||
ret = false;
|
||||
}
|
||||
|
||||
cv.notify_one();
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
callID = DRing::placeCall(accountID_, contact);
|
||||
|
||||
AGENT_INFO("Waiting for call %s", callID.c_str());
|
||||
|
||||
/* TODO - Parametize me */
|
||||
{
|
||||
std::unique_lock lk(mtx);
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&] { return success or over; });
|
||||
}
|
||||
|
||||
if (success) {
|
||||
AGENT_INFO("[call:%s] to %s: SUCCESS", callID.c_str(), contact.c_str());
|
||||
DRing::hangUp(callID);
|
||||
} else {
|
||||
AGENT_INFO("[call:%s] to %s: FAIL", callID.c_str(), contact.c_str());
|
||||
}
|
||||
|
||||
if (not over) {
|
||||
std::unique_lock lk(mtx);
|
||||
cv.wait_for(lk, std::chrono::seconds(30), [&] { return over; });
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
Agent::wait(std::chrono::seconds period)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::this_thread::sleep_for(period);
|
||||
}
|
||||
|
||||
void
|
||||
Agent::setDetails(const std::map<std::string, std::string>& details)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
auto cv = std::make_shared<std::condition_variable>();
|
||||
auto cont = std::make_shared<std::atomic<bool>>(true);
|
||||
|
||||
std::string info("Setting details:\n");
|
||||
|
||||
for (const auto& [key, value] : details) {
|
||||
info += key + " = " + value + "\n";
|
||||
}
|
||||
|
||||
AGENT_INFO("%s", info.c_str());
|
||||
|
||||
DRing::setAccountDetails(accountID_, details);
|
||||
}
|
||||
|
||||
std::map<std::string, std::string>
|
||||
Agent::getDetails() const
|
||||
{
|
||||
return DRing::getAccountDetails(accountID_);
|
||||
}
|
||||
|
||||
void
|
||||
Agent::activate(bool enable)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
DRing::sendRegister(accountID_, enable);
|
||||
|
||||
if (enable) {
|
||||
waitForAnnouncement();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Agent::exportToArchive(const std::string& path)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
AGENT_ASSERT(DRing::exportToFile(accountID_, path),
|
||||
"Failed to export account to `%s`",
|
||||
path.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
Agent::importFromArchive(const std::string& path)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::map<std::string, std::string> details;
|
||||
|
||||
details[DRing::Account::ConfProperties::TYPE] = "RING";
|
||||
details[DRing::Account::ConfProperties::DISPLAYNAME] = "AGENT";
|
||||
details[DRing::Account::ConfProperties::ALIAS] = "AGENT";
|
||||
details[DRing::Account::ConfProperties::ARCHIVE_PASSWORD] = "";
|
||||
details[DRing::Account::ConfProperties::ARCHIVE_PIN] = "";
|
||||
details[DRing::Account::ConfProperties::ARCHIVE_PATH] = path;
|
||||
|
||||
AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID");
|
||||
|
||||
details = DRing::getAccountDetails(accountID_);
|
||||
|
||||
waitForAnnouncement();
|
||||
|
||||
AGENT_ASSERT("AGENT" == details.at(DRing::Account::ConfProperties::DISPLAYNAME),
|
||||
"Bad display name");
|
||||
|
||||
peerID_ = details.at(DRing::Account::ConfProperties::USERNAME);
|
||||
conversations_ = DRing::getConversations(accountID_);
|
||||
|
||||
AGENT_INFO("Using account %s - %s", accountID_.c_str(), peerID_.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
Agent::ensureAccount()
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::map<std::string, std::string> details;
|
||||
|
||||
details = DRing::getAccountDetails(accountID_);
|
||||
|
||||
if (details.empty()) {
|
||||
details[DRing::Account::ConfProperties::TYPE] = "RING";
|
||||
details[DRing::Account::ConfProperties::DISPLAYNAME] = "AGENT";
|
||||
details[DRing::Account::ConfProperties::ALIAS] = "AGENT";
|
||||
details[DRing::Account::ConfProperties::ARCHIVE_PASSWORD] = "";
|
||||
details[DRing::Account::ConfProperties::ARCHIVE_PIN] = "";
|
||||
details[DRing::Account::ConfProperties::ARCHIVE_PATH] = "";
|
||||
|
||||
AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID");
|
||||
}
|
||||
|
||||
waitForAnnouncement();
|
||||
|
||||
details = DRing::getAccountDetails(accountID_);
|
||||
|
||||
AGENT_ASSERT("AGENT" == details.at(DRing::Account::ConfProperties::DISPLAYNAME),
|
||||
"Bad display name");
|
||||
|
||||
peerID_ = details.at(DRing::Account::ConfProperties::USERNAME);
|
||||
conversations_ = DRing::getConversations(accountID_);
|
||||
|
||||
AGENT_INFO("Using account %s - %s", accountID_.c_str(), peerID_.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
Agent::waitForCallState(const std::string& wanted)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::unique_lock lk(mtx);
|
||||
|
||||
onCallStateChanged_.add(
|
||||
[&](const std::string& /* call_id */, const std::string& state, signed /* code */) {
|
||||
if (wanted == state) {
|
||||
std::unique_lock lk(mtx);
|
||||
cv.notify_one();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
cv.wait(lk);
|
||||
}
|
||||
|
||||
Agent&
|
||||
Agent::instance()
|
||||
{
|
||||
static Agent agent;
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
void
|
||||
Agent::installSignalHandlers()
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
using std::bind;
|
||||
|
||||
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> handlers;
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::CallSignal::IncomingCallWithMedia>(
|
||||
bind(&Agent::Handler<const std::string&,
|
||||
const std::string&,
|
||||
const std::string&,
|
||||
const std::vector<DRing::MediaMap>>::execute,
|
||||
&onIncomingCall_,
|
||||
_1,
|
||||
_2,
|
||||
_3,
|
||||
_4)));
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::CallSignal::StateChange>(
|
||||
bind(&Agent::Handler<const std::string&, const std::string&, signed>::execute,
|
||||
&onCallStateChanged_,
|
||||
_1,
|
||||
_2,
|
||||
_3)));
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
|
||||
bind(&Agent::Handler<const std::string&,
|
||||
const std::string&,
|
||||
std::map<std::string, std::string>>::execute,
|
||||
&onMessageReceived_,
|
||||
_1,
|
||||
_2,
|
||||
_3)));
|
||||
|
||||
handlers.insert(
|
||||
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
|
||||
bind(&Agent::Handler<const std::string&,
|
||||
const std::string&,
|
||||
std::map<std::string, std::string>>::execute,
|
||||
&onConversationRequestReceived_,
|
||||
_1,
|
||||
_2,
|
||||
_3)));
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
|
||||
bind(&Agent::Handler<const std::string&, const std::string&>::execute,
|
||||
&onConversationReady_,
|
||||
_1,
|
||||
_2)));
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::ContactAdded>(
|
||||
bind(&Agent::Handler<const std::string&, const std::string&, bool>::execute,
|
||||
&onContactAdded_,
|
||||
_1,
|
||||
_2,
|
||||
_3)));
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::RegistrationStateChanged>(
|
||||
bind(&Agent::Handler<const std::string&, const std::string&, int, const std::string&>::execute,
|
||||
&onRegistrationStateChanged_,
|
||||
_1,
|
||||
_2,
|
||||
_3,
|
||||
_4)));
|
||||
|
||||
handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
|
||||
bind(&Agent::Handler<const std::string&, const std::map<std::string, std::string>&>::execute,
|
||||
&onVolatileDetailsChanged_,
|
||||
_1,
|
||||
_2)));
|
||||
|
||||
DRing::registerSignalHandlers(handlers);
|
||||
}
|
||||
|
||||
void
|
||||
Agent::registerStaticCallbacks()
|
||||
{
|
||||
onIncomingCall_.add([=](const std::string& accountID,
|
||||
const std::string& callID,
|
||||
const std::string& peerDisplayName,
|
||||
const std::vector<DRing::MediaMap> mediaList) {
|
||||
(void) accountID;
|
||||
(void) peerDisplayName;
|
||||
|
||||
std::string medias("");
|
||||
|
||||
for (const auto& media : mediaList) {
|
||||
for (const auto& [key, value] : media) {
|
||||
medias += key + "=" + value + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
AGENT_INFO("Incoming call `%s` from `%s` with medias:\n`%s`",
|
||||
callID.c_str(),
|
||||
peerDisplayName.c_str(),
|
||||
medias.c_str());
|
||||
|
||||
AGENT_ASSERT(DRing::acceptWithMedia(callID, mediaList),
|
||||
"Failed to accept call `%s`",
|
||||
callID.c_str());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
onMessageReceived_.add([=](const std::string& accountID,
|
||||
const std::string& conversationID,
|
||||
std::map<std::string, std::string> message) {
|
||||
(void) accountID;
|
||||
|
||||
/* Read only text message */
|
||||
if ("text/plain" != message.at("type")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto author = message.at("author");
|
||||
|
||||
/* Skip if sent by agent */
|
||||
if (peerID_ == author) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto msg = message.at("body");
|
||||
|
||||
AGENT_INFO("Incomming message `%s` from %s", msg.c_str(), author.c_str());
|
||||
|
||||
/* Echo back */
|
||||
if (0 != msg.rfind("PONG:", 0)) {
|
||||
DRing::sendMessage(accountID_, conversationID, "PONG:" + msg, "");
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
onConversationRequestReceived_.add([=](const std::string& accountID,
|
||||
const std::string& conversationID,
|
||||
std::map<std::string, std::string> meta) {
|
||||
(void) meta;
|
||||
|
||||
AGENT_INFO("Conversation request received for account %s", accountID.c_str());
|
||||
|
||||
DRing::acceptConversationRequest(accountID, conversationID);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
onConversationReady_.add([=](const std::string& accountID, const std::string& conversationID) {
|
||||
(void) accountID;
|
||||
conversations_.emplace_back(conversationID);
|
||||
return true;
|
||||
});
|
||||
|
||||
onContactAdded_.add([=](const std::string& accountID, const std::string& URI, bool confirmed) {
|
||||
AGENT_INFO("Contact added `%s` : %s", URI.c_str(), confirmed ? "accepted" : "refused");
|
||||
if (confirmed) {
|
||||
DRing::subscribeBuddy(accountID, URI, true);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
Agent::waitForAnnouncement(std::chrono::seconds timeout)
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
std::condition_variable cv;
|
||||
|
||||
std::mutex mtx;
|
||||
|
||||
onVolatileDetailsChanged_.add(
|
||||
[&](const std::string& accountID, const std::map<std::string, std::string>& details) {
|
||||
if (accountID_ != accountID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if ("true" != details.at(DRing::Account::VolatileProperties::DEVICE_ANNOUNCED)) {
|
||||
return true;
|
||||
}
|
||||
} catch (const std::out_of_range&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_lock lk(mtx);
|
||||
|
||||
cv.notify_one();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
std::unique_lock lk(mtx);
|
||||
|
||||
AGENT_ASSERT(std::cv_status::no_timeout == cv.wait_for(lk, timeout),
|
||||
"Timeout while waiting for account announcement on DHT");
|
||||
}
|
||||
|
||||
void
|
||||
Agent::init()
|
||||
{
|
||||
DRing::logging("console", "on");
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
installSignalHandlers();
|
||||
registerStaticCallbacks();
|
||||
}
|
||||
|
||||
void
|
||||
Agent::fini()
|
||||
{
|
||||
LOG_AGENT_STATE();
|
||||
|
||||
DRing::unregisterSignalHandlers();
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Olivier Dion <olivier.dion@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
|
||||
|
||||
/* Dring */
|
||||
#include "logger.h"
|
||||
#include "jami/jami.h"
|
||||
|
||||
/* std */
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define AGENT_ERR(FMT, ARGS...) JAMI_ERR("AGENT: " FMT, ##ARGS)
|
||||
#define AGENT_INFO(FMT, ARGS...) JAMI_INFO("AGENT: " FMT, ##ARGS)
|
||||
#define AGENT_DBG(FMT, ARGS...) JAMI_DBG("AGENT: " FMT, ##ARGS)
|
||||
#define AGENT_ASSERT(COND, MSG, ARGS...) \
|
||||
if (not(COND)) { \
|
||||
AGENT_ERR(MSG, ##ARGS); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
class Agent
|
||||
{
|
||||
template<typename... Args>
|
||||
class Handler
|
||||
{
|
||||
std::mutex mutex_;
|
||||
std::vector<std::function<bool(Args...)>> callbacks_;
|
||||
|
||||
public:
|
||||
void add(std::function<bool(Args...)>&& cb)
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(mutex_);
|
||||
callbacks_.emplace_back(std::move(cb));
|
||||
}
|
||||
|
||||
void execute(Args... args)
|
||||
{
|
||||
std::vector<std::function<bool(Args...)>> to_keep;
|
||||
std::unique_lock<std::mutex> lck(mutex_);
|
||||
|
||||
for (auto& cb : callbacks_) {
|
||||
if (cb(args...)) {
|
||||
to_keep.emplace_back(std::move(cb));
|
||||
}
|
||||
}
|
||||
|
||||
callbacks_.swap(to_keep);
|
||||
}
|
||||
};
|
||||
|
||||
/* Signal handlers */
|
||||
Handler<const std::string&, const std::string&, std::map<std::string, std::string>>
|
||||
onMessageReceived_;
|
||||
|
||||
Handler<const std::string&, const std::string&, std::map<std::string, std::string>>
|
||||
onConversationRequestReceived_;
|
||||
|
||||
Handler<const std::string&, const std::string&> onConversationReady_;
|
||||
|
||||
Handler<const std::string&, const std::string&, signed> onCallStateChanged_;
|
||||
|
||||
Handler<const std::string&,
|
||||
const std::string&,
|
||||
const std::string&,
|
||||
const std::vector<DRing::MediaMap>>
|
||||
onIncomingCall_;
|
||||
|
||||
Handler<const std::string&, const std::string&, bool> onContactAdded_;
|
||||
|
||||
Handler<const std::string&, const std::string&, int, const std::string&>
|
||||
onRegistrationStateChanged_;
|
||||
|
||||
Handler<const std::string&, const std::map<std::string, std::string>&>
|
||||
onVolatileDetailsChanged_;
|
||||
|
||||
/* Initialize agent */
|
||||
void installSignalHandlers();
|
||||
void registerStaticCallbacks();
|
||||
|
||||
/* Bookkeeping */
|
||||
std::string peerID_;
|
||||
const std::string accountID_ {"afafafafafafafaf"};
|
||||
std::vector<std::string> conversations_;
|
||||
|
||||
public:
|
||||
/* Behavior */
|
||||
bool ping(const std::string& conversation);
|
||||
bool placeCall(const std::string& contact);
|
||||
std::string someContact() const;
|
||||
std::string someConversation() const;
|
||||
void setDetails(const std::map<std::string, std::string>& details);
|
||||
std::map<std::string, std::string> getDetails() const;
|
||||
void stopRecording(const std::string& context);
|
||||
void startRecording(const std::string& context, const std::string& to);
|
||||
void searchForPeers(std::vector<std::string>& peers);
|
||||
void wait(std::chrono::seconds period);
|
||||
void exportToArchive(const std::string& path);
|
||||
void importFromArchive(const std::string& path);
|
||||
void ensureAccount();
|
||||
void waitForAnnouncement(std::chrono::seconds timeout=std::chrono::seconds(30));
|
||||
void activate(bool state);
|
||||
void waitForCallState(const std::string& wanted="CURRENT");
|
||||
|
||||
void init();
|
||||
void fini();
|
||||
|
||||
static Agent& instance();
|
||||
};
|
@ -1,320 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Olivier Dion <olivier.dion@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 "agent/agent.h"
|
||||
#include "agent/bindings.h"
|
||||
|
||||
static inline SCM
|
||||
to_guile(bool b)
|
||||
{
|
||||
return scm_from_bool(b);
|
||||
}
|
||||
|
||||
static inline SCM
|
||||
to_guile(const std::string& str)
|
||||
{
|
||||
return scm_from_utf8_string(str.c_str());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline SCM
|
||||
to_guile(const std::vector<T>& values)
|
||||
{
|
||||
SCM vec = scm_make_vector(values.size(), SCM_UNDEFINED);
|
||||
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
SCM_SIMPLE_VECTOR_SET(vec, i, to_guile(values[i]));
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
static inline SCM
|
||||
to_guile(const std::map<K, V>& map)
|
||||
{
|
||||
SCM assoc = SCM_EOL;
|
||||
|
||||
for (auto const& [key, value] : map) {
|
||||
SCM pair = scm_cons(to_guile(key), to_guile(value));
|
||||
assoc = scm_cons(pair, assoc);
|
||||
}
|
||||
|
||||
return assoc;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static inline SCM
|
||||
pack_to_guile(Args... args)
|
||||
{
|
||||
SCM lst = SCM_EOL;
|
||||
std::vector<SCM> values = {to_guile(args)...};
|
||||
|
||||
while (values.size()) {
|
||||
lst = scm_cons(values.back(), lst);
|
||||
values.pop_back();
|
||||
}
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static inline SCM
|
||||
apply_to_guile(SCM body_proc, Args... args)
|
||||
{
|
||||
AGENT_ASSERT(scm_is_true(scm_procedure_p(body_proc)),
|
||||
"body_proc must be a procedure");
|
||||
|
||||
SCM arglst = pack_to_guile(args...);
|
||||
|
||||
return scm_apply_0(body_proc, arglst);
|
||||
}
|
||||
|
||||
struct from_guile
|
||||
{
|
||||
SCM value;
|
||||
|
||||
from_guile(SCM val)
|
||||
: value(val)
|
||||
{}
|
||||
|
||||
operator bool()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_bool(value), "Scheme value must be of type bool");
|
||||
|
||||
return scm_to_bool(value);
|
||||
}
|
||||
|
||||
operator std::string()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_string(value), "Scheme value must be of type string");
|
||||
|
||||
char* str_raw = scm_to_locale_string(value);
|
||||
std::string ret(str_raw);
|
||||
free(str_raw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
operator std::vector<T>()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_simple_vector(value), "Scheme value must be a simple vector");
|
||||
|
||||
std::vector<T> ret;
|
||||
|
||||
for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(value); ++i) {
|
||||
SCM val = SCM_SIMPLE_VECTOR_REF(value, i);
|
||||
|
||||
ret.emplace_back(from_guile(val));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
operator std::map<K, V>()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_true(scm_list_p(value)), "Scheme value mut be a list");
|
||||
|
||||
std::map<K, V> ret;
|
||||
|
||||
while (not scm_is_null(value)) {
|
||||
SCM pair = scm_car(value);
|
||||
|
||||
K key = from_guile(scm_car(pair));
|
||||
V val = from_guile(scm_cdr(pair));
|
||||
|
||||
ret[key] = val;
|
||||
|
||||
value = scm_cdr(value);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
static SCM
|
||||
wait_binding(SCM period_long_optional)
|
||||
{
|
||||
std::chrono::seconds period;
|
||||
|
||||
if (SCM_UNBNDP(period_long_optional)) {
|
||||
period = std::chrono::seconds::max();
|
||||
} else {
|
||||
AGENT_ASSERT(scm_is_number(period_long_optional), "Invalid period");
|
||||
period = std::chrono::seconds(scm_to_uint(period_long_optional));
|
||||
}
|
||||
|
||||
Agent::instance().wait(period);
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
place_call_binding(SCM contact_str)
|
||||
{
|
||||
return to_guile(Agent::instance().placeCall(from_guile(contact_str)));
|
||||
}
|
||||
|
||||
static SCM
|
||||
some_conversation_binding()
|
||||
{
|
||||
return to_guile(Agent::instance().someConversation());
|
||||
}
|
||||
|
||||
static SCM
|
||||
some_contact_binding()
|
||||
{
|
||||
return to_guile(Agent::instance().someContact());
|
||||
}
|
||||
|
||||
static SCM
|
||||
search_peer_binding(SCM peers_vector_or_str)
|
||||
{
|
||||
std::vector<std::string> peers;
|
||||
|
||||
if (scm_is_string(peers_vector_or_str)) {
|
||||
peers.emplace_back(from_guile(peers_vector_or_str));
|
||||
} else {
|
||||
peers = from_guile(peers_vector_or_str);
|
||||
}
|
||||
|
||||
Agent::instance().searchForPeers(peers);
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
ping_binding(SCM contact_str)
|
||||
{
|
||||
return to_guile(Agent::instance().ping(from_guile(contact_str)));
|
||||
}
|
||||
|
||||
static SCM
|
||||
set_details_binding(SCM details_alist)
|
||||
{
|
||||
Agent::instance().setDetails(from_guile(details_alist));
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
get_details_binding()
|
||||
{
|
||||
return to_guile(Agent::instance().getDetails());
|
||||
}
|
||||
|
||||
static SCM
|
||||
ensure_account_binding()
|
||||
{
|
||||
Agent::instance().ensureAccount();
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
export_to_archive_binding(SCM path)
|
||||
{
|
||||
Agent::instance().exportToArchive(from_guile(path));
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
import_from_archive_binding(SCM path)
|
||||
{
|
||||
Agent::instance().importFromArchive(from_guile(path));
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
enable_binding()
|
||||
{
|
||||
Agent::instance().activate(true);
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
disable_binding()
|
||||
{
|
||||
Agent::instance().activate(false);
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
static SCM
|
||||
wait_for_call_state(SCM wanted_state_str)
|
||||
{
|
||||
Agent::instance().waitForCallState(from_guile(wanted_state_str));
|
||||
|
||||
return SCM_UNDEFINED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register Guile bindings here.
|
||||
*
|
||||
* 1. Name of the binding
|
||||
* 2. Number of required argument to binding
|
||||
* 3. Number of optional argument to binding
|
||||
* 4. Number of rest argument to binding
|
||||
* 5. Pointer to C function to call
|
||||
*
|
||||
* See info guile:
|
||||
*
|
||||
* Function: SCM scm_c_define_gsubr(const char *name, int req, int opt, int rst, fcn):
|
||||
*
|
||||
* Register a C procedure FCN as a “subr” — a primitive subroutine that can be
|
||||
* called from Scheme. It will be associated with the given NAME and bind it in
|
||||
* the "current environment". The arguments REQ, OPT and RST specify the number
|
||||
* of required, optional and “rest” arguments respectively. The total number of
|
||||
* these arguments should match the actual number of arguments to FCN, but may
|
||||
* not exceed 10. The number of rest arguments should be 0 or 1.
|
||||
* ‘scm_c_make_gsubr’ returns a value of type ‘SCM’ which is a “handle” for the
|
||||
* procedure.
|
||||
*/
|
||||
void
|
||||
install_scheme_primitives()
|
||||
{
|
||||
auto define_primitive = [](const char* name, int req, int opt, int rst, void* func) {
|
||||
AGENT_ASSERT(req + opt + rst <= 10, "Primitive binding `%s` has too many argument", name);
|
||||
|
||||
AGENT_ASSERT(0 == rst or 1 == rst, "Rest argument for binding `%s` must be 0 or 1", name);
|
||||
|
||||
scm_c_define_gsubr(name, req, opt, rst, func);
|
||||
};
|
||||
|
||||
define_primitive("agent:search-for-peers", 1, 0, 0, (void*) search_peer_binding);
|
||||
define_primitive("agent:place-call", 1, 0, 0, (void*) place_call_binding);
|
||||
define_primitive("agent:some-contact", 0, 0, 0, (void*) some_contact_binding);
|
||||
define_primitive("agent:some-conversation", 0, 0, 0, (void*) some_conversation_binding);
|
||||
define_primitive("agent:wait", 0, 1, 0, (void*) wait_binding);
|
||||
define_primitive("agent:ping", 1, 0, 0, (void*) ping_binding);
|
||||
define_primitive("agent:set-details", 1, 0, 0, (void*) set_details_binding);
|
||||
define_primitive("agent:get-details", 0, 0, 0, (void*) get_details_binding);
|
||||
define_primitive("agent:ensure-account", 0, 0, 0, (void*) ensure_account_binding);
|
||||
define_primitive("agent->archive", 1, 0, 0, (void*) export_to_archive_binding);
|
||||
define_primitive("archive->agent", 1, 0, 0, (void*) import_from_archive_binding);
|
||||
define_primitive("agent:enable", 0, 0, 0, (void*) enable_binding);
|
||||
define_primitive("agent:disable", 0, 0, 0, (void*) disable_binding);
|
||||
define_primitive("agent:wait-for-call-state", 1, 0, 0, (void*) wait_for_call_state);
|
||||
}
|
63
test/agent/src/bindings/bindings.cpp
Normal file
63
test/agent/src/bindings/bindings.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Olivier Dion <olivier.dion@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.
|
||||
*/
|
||||
|
||||
/* Agent */
|
||||
#include "bindings/bindings.h"
|
||||
|
||||
/* Include module's bindings here */
|
||||
|
||||
void
|
||||
install_scheme_primitives()
|
||||
{
|
||||
/* Define modules here */
|
||||
}
|
||||
|
||||
/*
|
||||
* Register Guile bindings here.
|
||||
*
|
||||
* 1. Name of the binding
|
||||
* 2. Number of required argument to binding
|
||||
* 3. Number of optional argument to binding
|
||||
* 4. Number of rest argument to binding
|
||||
* 5. Pointer to C function to call
|
||||
*
|
||||
* See info guile:
|
||||
*
|
||||
* Function: SCM scm_c_define_gsubr(const char *name, int req, int opt, int rst, fcn):
|
||||
*
|
||||
* Register a C procedure FCN as a “subr” — a primitive subroutine that can be
|
||||
* called from Scheme. It will be associated with the given NAME and bind it in
|
||||
* the "current environment". The arguments REQ, OPT and RST specify the number
|
||||
* of required, optional and “rest” arguments respectively. The total number of
|
||||
* these arguments should match the actual number of arguments to FCN, but may
|
||||
* not exceed 10. The number of rest arguments should be 0 or 1.
|
||||
* ‘scm_c_make_gsubr’ returns a value of type ‘SCM’ which is a “handle” for the
|
||||
* procedure.
|
||||
*/
|
||||
void
|
||||
define_primitive(const char* name, int req, int opt, int rst, void* func)
|
||||
{
|
||||
AGENT_ASSERT(req + opt + rst <= 10, "Primitive binding `%s` has too many argument", name);
|
||||
|
||||
AGENT_ASSERT(0 == rst or 1 == rst, "Rest argument for binding `%s` must be 0 or 1", name);
|
||||
|
||||
scm_c_define_gsubr(name, req, opt, rst, func);
|
||||
scm_c_export(name, NULL);
|
||||
}
|
@ -20,8 +20,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Guile */
|
||||
#include <libguile.h>
|
||||
|
||||
#define scm_to_cxx_string(VAR) (scm_to_cxx_string)(VAR, #VAR)
|
||||
/* Agent */
|
||||
#include "utils.h"
|
||||
|
||||
extern void define_primitive(const char* name, int req, int opt, int rst, void* func);
|
||||
extern void install_scheme_primitives();
|
@ -18,56 +18,39 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* agent */
|
||||
#include "agent/agent.h"
|
||||
#include "agent/bindings.h"
|
||||
/* Agent */
|
||||
#include "bindings/bindings.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Jami */
|
||||
#include "jami.h"
|
||||
|
||||
/* Third parties */
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <libguile.h>
|
||||
|
||||
/* std */
|
||||
#include <fstream>
|
||||
|
||||
static void
|
||||
fini()
|
||||
{
|
||||
Agent::instance().fini();
|
||||
DRing::fini();
|
||||
}
|
||||
|
||||
struct args {
|
||||
int argc;
|
||||
char** argv;
|
||||
};
|
||||
|
||||
void*
|
||||
main_inner(void* args_raw) /* In Guile context */
|
||||
main_in_guile(void* args_raw)
|
||||
{
|
||||
struct args* args = (struct args*)args_raw;
|
||||
struct args* args = static_cast<struct args*>(args_raw);
|
||||
|
||||
install_scheme_primitives();
|
||||
|
||||
Agent::instance().init();
|
||||
|
||||
atexit(fini);
|
||||
atexit(DRing::fini);
|
||||
|
||||
scm_shell(args->argc, args->argv);
|
||||
|
||||
/* unreachable */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
if (argc < 2) {
|
||||
printf("Usage: agent CONFIG\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
setenv("GUILE_LOAD_PATH", ".", 1);
|
||||
|
||||
/* NOTE! It's very important to initialize the daemon before entering Guile!!! */
|
||||
@ -75,11 +58,8 @@ main(int argc, char* argv[])
|
||||
|
||||
AGENT_ASSERT(DRing::start(""), "Failed to start daemon");
|
||||
|
||||
struct args args;
|
||||
struct args args = { argc, argv };
|
||||
|
||||
args.argc = argc;
|
||||
args.argv = argv;
|
||||
|
||||
/* Entering guile context */
|
||||
scm_with_guile(main_inner, (void*)&args);
|
||||
/* Entering guile context - This never returns */
|
||||
scm_with_guile(main_in_guile, (void*)&args);
|
||||
}
|
168
test/agent/src/utils.h
Normal file
168
test/agent/src/utils.h
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Olivier Dion <olivier.dion@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 "logger.h"
|
||||
|
||||
#define AGENT_ERR(FMT, ARGS...) JAMI_ERR("AGENT: " FMT, ##ARGS)
|
||||
#define AGENT_INFO(FMT, ARGS...) JAMI_INFO("AGENT: " FMT, ##ARGS)
|
||||
#define AGENT_DBG(FMT, ARGS...) JAMI_DBG("AGENT: " FMT, ##ARGS)
|
||||
#define AGENT_ASSERT(COND, MSG, ARGS...) \
|
||||
if (not(COND)) { \
|
||||
AGENT_ERR(MSG, ##ARGS); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
static inline SCM
|
||||
to_guile(bool b)
|
||||
{
|
||||
return scm_from_bool(b);
|
||||
}
|
||||
|
||||
static inline SCM
|
||||
to_guile(const std::string& str)
|
||||
{
|
||||
return scm_from_utf8_string(str.c_str());
|
||||
}
|
||||
|
||||
/* Forward declarations since we call to_guile() recursively for containers */
|
||||
template<typename T> static inline SCM to_guile(const std::vector<T>& values);
|
||||
template<typename K, typename V> static inline SCM to_guile(const std::map<K, V>& map);
|
||||
|
||||
template<typename T>
|
||||
static inline SCM
|
||||
to_guile(const std::vector<T>& values)
|
||||
{
|
||||
SCM vec = scm_c_make_vector(values.size(), SCM_UNDEFINED);
|
||||
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
SCM_SIMPLE_VECTOR_SET(vec, i, to_guile(values[i]));
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
static inline SCM
|
||||
to_guile(const std::map<K, V>& map)
|
||||
{
|
||||
SCM assoc = SCM_EOL;
|
||||
|
||||
for (auto const& [key, value] : map) {
|
||||
SCM pair = scm_cons(to_guile(key), to_guile(value));
|
||||
assoc = scm_cons(pair, assoc);
|
||||
}
|
||||
|
||||
return assoc;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static inline SCM
|
||||
pack_to_guile(Args... args)
|
||||
{
|
||||
SCM lst = SCM_EOL;
|
||||
std::vector<SCM> values = {to_guile(args)...};
|
||||
|
||||
while (values.size()) {
|
||||
lst = scm_cons(values.back(), lst);
|
||||
values.pop_back();
|
||||
}
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static inline SCM
|
||||
apply_to_guile(SCM body_proc, Args... args)
|
||||
{
|
||||
AGENT_ASSERT(scm_is_true(scm_procedure_p(body_proc)),
|
||||
"body_proc must be a procedure");
|
||||
|
||||
SCM arglst = pack_to_guile(args...);
|
||||
|
||||
return scm_apply_0(body_proc, arglst);
|
||||
}
|
||||
|
||||
struct from_guile
|
||||
{
|
||||
SCM value;
|
||||
|
||||
from_guile(SCM val)
|
||||
: value(val)
|
||||
{}
|
||||
|
||||
operator bool()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_bool(value), "Scheme value must be of type bool");
|
||||
|
||||
return scm_to_bool(value);
|
||||
}
|
||||
|
||||
operator std::string()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_string(value), "Scheme value must be of type string");
|
||||
|
||||
char* str_raw = scm_to_locale_string(value);
|
||||
std::string ret(str_raw);
|
||||
free(str_raw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
operator std::vector<T>()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_simple_vector(value), "Scheme value must be a simple vector");
|
||||
|
||||
std::vector<T> ret;
|
||||
|
||||
ret.reserve(SCM_SIMPLE_VECTOR_LENGTH(value));
|
||||
|
||||
for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(value); ++i) {
|
||||
SCM val = SCM_SIMPLE_VECTOR_REF(value, i);
|
||||
|
||||
ret.emplace_back(from_guile(val));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
operator std::map<K, V>()
|
||||
{
|
||||
AGENT_ASSERT(scm_is_true(scm_list_p(value)), "Scheme value mut be a list");
|
||||
|
||||
std::map<K, V> ret;
|
||||
|
||||
while (not scm_is_null(value)) {
|
||||
SCM pair = scm_car(value);
|
||||
|
||||
K key = from_guile(scm_car(pair));
|
||||
V val = from_guile(scm_cdr(pair));
|
||||
|
||||
ret[key] = val;
|
||||
|
||||
value = scm_cdr(value);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user