APIs

OpenDHT

The documentation related to the API of OpenDHT is here and will not be detailed in the following part.

Daemon

The managers

The API of the daemon is decomposed between 5 Managers + 1 Instance file:

  • The CallManager interface is used to manage call and conference related actions. Since Ring-daemon supports multiple incoming/outgoing calls, any actions involving a specific call must address the method by the means of a unique callID. Ring-daemon will generate a unique callID for outgoing and incoming calls.

  • The ConfigurationManager used to handle the configuration stuff: accounts settings, user preferences, …

  • The PresenceManager is used to track the presence of contacts

  • The VideoManager used to manage video devices and renderers

  • The Instance is used to count the number of clients actually registered to the core. When initializing your client, you need to register it against the core by using this interface.

DBUS

All the documentation and code for the dbus API is located in ring-daemon/bin/dbus.

If you use linux, you can use d-feet when the daemon is running to manipulate the API (or with any another tool).

The LRC project uses this API (and use libwrap on windows and mac os).

JNI

All the documentation and code for the JNI API is located in ring-daemon/bin/jni.

Generation process

  • cd ring-project/

  • Check ./daemon/src/dring

  • Create a new file .i in ./daemon/bin/jni/ from an existing file in the same folder

  • Update with your new interfaces from documentation

  • Check that callback block is at the beginning AND at the end of file

  • cd daemon/bin/jni

  • Edit ./daemon/bin/jni/jni_interface.i and add yours with the other .i files that are in the same folder

  • export PACKAGEDIR=\*\*\*\*/ring-project/client-android/ring-android/libringclient/src/main/java/cx/ring/daemon. ie: export PACKAGEDIR=/home/pduchemin/Documents/ring-project/client-android/ring-android/libringclient/src/main/java/cx/ring/daemon

  • Run

    • ./make-swig.sh

  • or, if you start from scratch

    • ./make-ring.py --init --distribution=Android

    • ./make-ring.py --install --distribution=Android

node js

All the documentation and code for the Node JS API is located in ring-daemon/bin/nodejs. This API is not used in any known project and maybe is not up-to-date.

REST

All the documentation and code for the REST API is located in ring-daemon/bin/restcpp. This API is not used in any known project and maybe is not up-to-date.

Python wrapper

A Python wrapper is available in ring-daemon/tools/dringctrl. This wrapper uses d-bus.

This is, for example a quick and dirty IRC bot to interact with Ring made with this API:

config.json:

{
	"users": {
	},
	"debug": true,
	"host": "irc.freenode.net",
	"follow_all": {
	},
	"port": 6697,
	"nick": "NICKNAME",
	"ssl": true,
	"channel": "#REPLACE"
}

irc,py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from controler import DRingCtrl
from threading import Thread
import irc3
from irc3 import utils
import json
import sys


@irc3.plugin
class RingBridge:
	'''This plug-in react to event from IRC and send messages to IRC'''

	requires = [
		'irc3.plugins.core',
		'irc3.plugins.userlist',
		'irc3.plugins.command',
		'irc3.plugins.human',
	]

	def __init__(self, bot):
		self.bot = bot
		self.log = self.bot.log
		self.channels = utils.as_list(self.bot.config.get('autojoins', []))
		self.config = None

	@irc3.event(irc3.rfc.PRIVMSG)
	def on_privmsg(self, mask=None, target=None, data=None, **kwargs):
		'''React to messages from IRC'''
		message = data.split(' ')
		follow_all = self.bot.config_bridge['follow_all']
		if message[0] == '!tell':
			msg_to_send = ('%s: ' % mask.nick) + ''.join(
				'%s ' % m for m in message[2:])[:-1]
			call_id = None
			users = self.bot.config_bridge['users']
			# Get ring_id from nick
			for ring_id in users.keys():
				if users[ring_id].lower() == message[1].lower():
					call_id = ring_id
			if call_id:
				# if this nick want to receive all messages, we don't need
				# to send the message here
				if call_id not in follow_all.keys() \
					or not follow_all[call_id] \
						or target == self.bot.config_bridge['channel']:
					print('Send: %s to %s' % (msg_to_send, call_id))
					self.bot.controler.sendTextMessage(call_id,
													   msg_to_send)
			else:
				self.bot.privmsg(self.bot.config_bridge['channel'],
								 'I don\'t know how to contact this person')
		# Send message to everyone who wants to receive message from #channel
		if target.lower() != self.bot.config_bridge['channel'].lower():
			return
		msg_to_send = ('%s: %s' % (mask.nick, data))
		for user in follow_all.keys():
			if follow_all[user]:
				self.bot.controler.sendTextMessage(user,
												   msg_to_send)

	@irc3.extend
	def send_message_to_irc(self, ring_id, message):
		'''send message to #channel'''
		self.bot.privmsg(self.bot.config_bridge['channel'],
						 '%s: %s' %
						 (self.bot.config_bridge['users'][ring_id], message),
						 True)


def threadClient(controler, config_bridge):
	config = dict(
		nick=config_bridge['nick'],
		autojoins=[config_bridge['channel']],
		host=config_bridge['host'],
		port=config_bridge['port'],
		ssl=config_bridge['ssl'],
		debug=config_bridge['debug'],
		includes=[__name__]
	)
	bot = irc3.IrcBot.from_config(config)
	# link irc and ring controlers
	bot.controler = controler
	bot.config_bridge = config_bridge
	controler.irc_client = bot

	bot.run(forever=True)


class IRCRingController(DRingCtrl):
	def __init__(self, config_bridge):
		super().__init__('ircbridge', True)
		self.irc_client = None
		self.config_bridge = config_bridge
		self.accountId = self.configurationmanager.getAccountList()[0]

	def updateConfig(self):
		'''Change actual config'''
		with open('config.json', 'w') as config_file:
			json.dump(self.config_bridge, config_file, indent=4)
		if self.irc_client:
			self.irc_client.config_bridge = self.config_bridge

	def onIncomingAccountMessage(self, accountId, fromAccount, payloads):
		'''React to message from Ring'''
		# Avoid to react to message from self.
		if fromAccount == self.accountId:
			return
		# If we have multiple accounts, avoid to react from another account
		if accountId != self.accountId:
			return
		message = '%s: %s' % (fromAccount, payloads['text/plain'])
		print('Receive new message from %s' % message)
		# React to !commands
		if payloads['text/plain'] == '!follow':
			self.config_bridge['follow_all'][fromAccount] = True
			self.updateConfig()
		elif payloads['text/plain'] == '!unfollow':
			self.config_bridge['follow_all'][fromAccount] = False
			self.updateConfig()
		elif payloads['text/plain'].split(' ')[0] == '!add':
			irc_nick = payloads['text/plain'].split(' ')[1]
			self.config_bridge['users'][fromAccount] = irc_nick
			self.updateConfig()
		elif payloads['text/plain'] == '!rm':
			del self.config_bridge['users'][fromAccount]
			del self.config_bridge['follow_all'][fromAccount]
			self.updateConfig()
		# Send message to IRC
		else:
			try:
				if self.irc_client:
					self.irc_client.send_message_to_irc(fromAccount,
														payloads['text/plain'])
			except:
				print('Can\'t read message received: %s' % payloads)

	def sendTextMessage(self, accountId, message):
		'''Send a message to a ring id'''
		if accountId == self.accountId:
			return
		self.configurationmanager.sendTextMessage(self.accountId,
												  accountId,
												  {
													'text/plain':
													str(message)
												  })


if __name__ == '__main__':
	config_bridge = None
	with open('config.json') as config_file:
		config_bridge = json.loads(config_file.read())
	if not config_bridge:
		print('Can\'t find config.json')
		sys.exit(-1)
	irc_controler = IRCRingController(config_bridge)
	thread = Thread(target=threadClient, args=(irc_controler, config_bridge,))
	thread.start()
	irc_controler.run()

LRC

Doxygen doc

The Doxygen documentation is available here and currently generated by Jenkins each week.

Database schema

CREATE TABLE profiles (id INTEGER PRIMARY KEY,  \
					   uri TEXT NOT NULL,       \
					   alias TEXT,              \
					   photo TEXT,              \
					   type TEXT,               \
					   status TEXT);

CREATE TABLE conversations (id INTEGER,\
							participant_id INTEGER, \
							FOREIGN KEY(participant_id) REFERENCES profiles(id));

CREATE TABLE interactions (id INTEGER PRIMARY KEY,\
						   account_id INTEGER, \
						   author_id INTEGER, \
						   conversation_id INTEGER, \
						   timestamp INTEGER, \
						   body TEXT,     \
						   type TEXT,  \
						   status TEXT, \
						   daemon_id TEXT, \
						   FOREIGN KEY(account_id) REFERENCES profiles(id), \
						   FOREIGN KEY(author_id) REFERENCES profiles(id), \
						   FOREIGN KEY(conversation_id) REFERENCES conversations(id));

CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL,                    \
								account_id TEXT NOT NULL,                       \
								is_account TEXT,                                \
								FOREIGN KEY(profile_id) REFERENCES profiles(id));