Skip to content
Snippets Groups Projects
Commit aa3299b3 authored by Olivier Dion's avatar Olivier Dion Committed by Adrien Béraud
Browse files

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
parent bfa324bc
No related branches found
No related tags found
No related merge requests found
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
/*
* 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();
}
/*
* 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();
};
/*
* 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;
args.argc = argc;
args.argv = argv;
struct args args = { argc, 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);
}
......@@ -18,8 +18,18 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "agent/agent.h"
#include "agent/bindings.h"
#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)
......@@ -33,11 +43,15 @@ 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_make_vector(values.size(), SCM_UNDEFINED);
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]));
......@@ -120,6 +134,8 @@ struct from_guile
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);
......@@ -150,171 +166,3 @@ struct from_guile
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);
}
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