summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/python
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-07-18 23:00:56 +0000
committerBenny Prijono <bennylp@teluu.com>2008-07-18 23:00:56 +0000
commita331abeec9382f40293a5b3c7e4dc2163f6ad734 (patch)
treef130641ab7dfac5b00518f1eee8eabedad817242 /pjsip-apps/src/python
parent63ba5ff8c3c834b685b4e5f41833f77737008193 (diff)
Implemented ticket #192 for Python: Add callback to notify application about incoming SUBSCRIBE request, and add subscription state and termination reason in buddy info
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2156 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip-apps/src/python')
-rw-r--r--pjsip-apps/src/python/_pjsua.c119
-rw-r--r--pjsip-apps/src/python/_pjsua.h30
-rw-r--r--pjsip-apps/src/python/pjsua.py93
-rw-r--r--pjsip-apps/src/python/samples/subscribe.py94
4 files changed, 323 insertions, 13 deletions
diff --git a/pjsip-apps/src/python/_pjsua.c b/pjsip-apps/src/python/_pjsua.c
index ab55077e..6590dd5c 100644
--- a/pjsip-apps/src/python/_pjsua.c
+++ b/pjsip-apps/src/python/_pjsua.c
@@ -340,6 +340,58 @@ static void cb_on_reg_state(pjsua_acc_id acc_id)
}
}
+/*
+ * cb_on_incoming_subscribe
+ */
+static void cb_on_incoming_subscribe( pjsua_acc_id acc_id,
+ pjsua_srv_pres *srv_pres,
+ pjsua_buddy_id buddy_id,
+ const pj_str_t *from,
+ pjsip_rx_data *rdata,
+ pjsip_status_code *code,
+ pj_str_t *reason,
+ pjsua_msg_data *msg_data)
+{
+ static char reason_buf[64];
+
+ PJ_UNUSED_ARG(rdata);
+ PJ_UNUSED_ARG(msg_data);
+
+ if (PyCallable_Check(g_obj_callback->on_incoming_subscribe))
+ {
+ PyObject *ret;
+
+ ENTER_PYTHON();
+
+ ret = PyObject_CallFunctionObjArgs(
+ g_obj_callback->on_incoming_subscribe,
+ Py_BuildValue("i", acc_id),
+ Py_BuildValue("i", buddy_id),
+ PyString_FromStringAndSize(from->ptr, from->slen),
+ PyLong_FromLong((long)srv_pres),
+ NULL
+ );
+
+ if (ret && PyTuple_Check(ret)) {
+ if (PyTuple_Size(ret) >= 1)
+ *code = (int)PyInt_AsLong(PyTuple_GetItem(ret, 0));
+ if (PyTuple_Size(ret) >= 2) {
+ if (PyTuple_GetItem(ret, 1) != Py_None) {
+ pj_str_t tmp;
+ tmp = PyString_to_pj_str(PyTuple_GetItem(ret, 1));
+ reason->ptr = reason_buf;
+ pj_strncpy(reason, &tmp, sizeof(reason_buf));
+ } else {
+ }
+ }
+
+ } else if (ret) {
+ Py_XDECREF(ret);
+ }
+
+ LEAVE_PYTHON();
+ }
+}
/*
* cb_on_buddy_state
@@ -371,6 +423,8 @@ static void cb_on_pager(pjsua_call_id call_id, const pj_str_t *from,
const pj_str_t *mime_type, const pj_str_t *body,
pjsip_rx_data *rdata, pjsua_acc_id acc_id)
{
+ PJ_UNUSED_ARG(rdata);
+
if (PyCallable_Check(g_obj_callback->on_pager))
{
ENTER_PYTHON();
@@ -911,6 +965,7 @@ static PyObject *py_pjsua_init(PyObject *pSelf, PyObject *pArgs)
cfg_ua.cb.on_call_replace_request = &cb_on_call_replace_request;
cfg_ua.cb.on_call_replaced = &cb_on_call_replaced;
cfg_ua.cb.on_reg_state = &cb_on_reg_state;
+ cfg_ua.cb.on_incoming_subscribe = &cb_on_incoming_subscribe;
cfg_ua.cb.on_buddy_state = &cb_on_buddy_state;
cfg_ua.cb.on_pager2 = &cb_on_pager;
cfg_ua.cb.on_pager_status2 = &cb_on_pager_status;
@@ -1879,6 +1934,66 @@ static PyObject *py_pjsua_acc_set_transport
}
+/*
+ * py_pjsua_acc_pres_notify
+ */
+static PyObject *py_pjsua_acc_pres_notify
+(PyObject *pSelf, PyObject *pArgs)
+{
+ static char reason_buf[64];
+ int acc_id, state;
+ PyObject *arg_pres, *arg_msg_data;
+ void *srv_pres;
+ pjsua_msg_data msg_data;
+ const char *arg_reason;
+ pj_str_t reason;
+ pj_bool_t with_body;
+ pj_pool_t *pool = NULL;
+ int status;
+
+ PJ_UNUSED_ARG(pSelf);
+
+ if (!PyArg_ParseTuple(pArgs, "iOisO", &acc_id, &arg_pres,
+ &state, &arg_reason, &arg_msg_data))
+ {
+ return NULL;
+ }
+
+ srv_pres = (void*) PyLong_AsLong(arg_pres);
+ pjsua_msg_data_init(&msg_data);
+ with_body = (state != PJSIP_EVSUB_STATE_TERMINATED);
+
+ if (arg_reason) {
+ strncpy(reason_buf, arg_reason, sizeof(reason_buf));
+ reason.ptr = reason_buf;
+ reason.slen = strlen(arg_reason);
+ } else {
+ reason = pj_str("");
+ }
+
+ if (arg_msg_data && arg_msg_data != Py_None) {
+ PyObj_pjsua_msg_data *omd = (PyObj_pjsua_msg_data *)arg_msg_data;
+ msg_data.content_type.ptr = PyString_AsString(omd->content_type);
+ msg_data.content_type.slen = PyString_Size(omd->content_type);
+ msg_data.msg_body.ptr = PyString_AsString(omd->msg_body);
+ msg_data.msg_body.slen = PyString_Size(omd->msg_body);
+ pool = pjsua_pool_create("pytmp", POOL_SIZE, POOL_SIZE);
+ translate_hdr(pool, &msg_data.hdr_list, omd->hdr_list);
+ } else if (arg_msg_data) {
+ Py_XDECREF(arg_msg_data);
+ }
+
+ status = pjsua_pres_notify(acc_id, (pjsua_srv_pres*)srv_pres,
+ (pjsip_evsub_state)state, NULL,
+ &reason, with_body, &msg_data);
+
+ if (pool) {
+ pj_pool_release(pool);
+ }
+
+ return Py_BuildValue("i", status);
+}
+
static char pjsua_acc_config_default_doc[] =
"_pjsua.Acc_Config _pjsua.acc_config_default () "
"Call this function to initialize account config with default values.";
@@ -5542,6 +5657,10 @@ static PyMethodDef py_pjsua_methods[] =
pjsua_acc_get_info_doc
},
{
+ "acc_pres_notify", py_pjsua_acc_pres_notify, METH_VARARGS,
+ "Accept or reject subscription request"
+ },
+ {
"enum_accs", py_pjsua_enum_accs, METH_VARARGS,
pjsua_enum_accs_doc
},
diff --git a/pjsip-apps/src/python/_pjsua.h b/pjsip-apps/src/python/_pjsua.h
index 269c30df..8c16c0f9 100644
--- a/pjsip-apps/src/python/_pjsua.h
+++ b/pjsip-apps/src/python/_pjsua.h
@@ -507,6 +507,7 @@ typedef struct PyObj_pjsua_callback
PyObject * on_call_replace_request;
PyObject * on_call_replaced;
PyObject * on_reg_state;
+ PyObject * on_incoming_subscribe;
PyObject * on_buddy_state;
PyObject * on_pager;
PyObject * on_pager_status;
@@ -529,6 +530,7 @@ static void PyObj_pjsua_callback_delete(PyObj_pjsua_callback* self)
Py_XDECREF(self->on_call_replace_request);
Py_XDECREF(self->on_call_replaced);
Py_XDECREF(self->on_reg_state);
+ Py_XDECREF(self->on_incoming_subscribe);
Py_XDECREF(self->on_buddy_state);
Py_XDECREF(self->on_pager);
Py_XDECREF(self->on_pager_status);
@@ -616,6 +618,8 @@ static PyObject * PyObj_pjsua_callback_new(PyTypeObject *type,
Py_DECREF(Py_None);
return NULL;
}
+ Py_INCREF(Py_None);
+ self->on_incoming_subscribe = Py_None;
Py_INCREF(Py_None);
self->on_buddy_state = Py_None;
if (self->on_buddy_state == NULL)
@@ -719,6 +723,11 @@ static PyMemberDef PyObj_pjsua_callback_members[] =
"may then query the account info to get the registration details."
},
{
+ "on_incoming_subscribe", T_OBJECT_EX,
+ offsetof(PyObj_pjsua_callback, on_incoming_subscribe), 0,
+ "Notification when incoming SUBSCRIBE request is received."
+ },
+ {
"on_buddy_state", T_OBJECT_EX,
offsetof(PyObj_pjsua_callback, on_buddy_state), 0,
"Notify application when the buddy state has changed. Application may "
@@ -2768,6 +2777,8 @@ typedef struct
PyObject *status_text;
int monitor_pres;
int activity;
+ int sub_state;
+ PyObject *sub_term_reason;
} PyObj_pjsua_buddy_info;
@@ -2781,6 +2792,7 @@ static void PyObj_pjsua_buddy_info_delete(PyObj_pjsua_buddy_info* self)
Py_XDECREF(self->uri);
Py_XDECREF(self->contact);
Py_XDECREF(self->status_text);
+ Py_XDECREF(self->sub_term_reason);
self->ob_type->tp_free((PyObject*)self);
}
@@ -2800,6 +2812,10 @@ static void PyObj_pjsua_buddy_info_import(PyObj_pjsua_buddy_info *obj,
info->status_text.slen);
obj->monitor_pres = info->monitor_pres;
obj->activity = info->rpid.activity;
+ obj->sub_state = info->sub_state;
+ Py_XDECREF(obj->sub_term_reason);
+ obj->sub_term_reason = PyString_FromStringAndSize(info->sub_term_reason.ptr,
+ info->sub_term_reason.slen);
}
@@ -2823,7 +2839,7 @@ static PyObject * PyObj_pjsua_buddy_info_new(PyTypeObject *type,
if (self->uri == NULL) {
Py_DECREF(self);
return NULL;
- }
+ }
self->contact = PyString_FromString("");
if (self->contact == NULL) {
Py_DECREF(self);
@@ -2834,7 +2850,7 @@ static PyObject * PyObj_pjsua_buddy_info_new(PyTypeObject *type,
Py_DECREF(self);
return NULL;
}
-
+ self->sub_term_reason = PyString_FromString("");
}
return (PyObject *)self;
}
@@ -2882,6 +2898,16 @@ static PyMemberDef PyObj_pjsua_buddy_info_members[] =
offsetof(PyObj_pjsua_buddy_info, activity), 0,
"Activity type. "
},
+ {
+ "sub_state", T_INT,
+ offsetof(PyObj_pjsua_buddy_info, sub_state), 0,
+ "Subscription state."
+ },
+ {
+ "sub_term_reason", T_INT,
+ offsetof(PyObj_pjsua_buddy_info, sub_term_reason), 0,
+ "Subscription termination reason."
+ },
{NULL} /* Sentinel */
diff --git a/pjsip-apps/src/python/pjsua.py b/pjsip-apps/src/python/pjsua.py
index df7d7358..65069440 100644
--- a/pjsip-apps/src/python/pjsua.py
+++ b/pjsip-apps/src/python/pjsua.py
@@ -147,13 +147,13 @@ class MediaState:
Member documentation:
- NONE -- media is not available.
+ NULL -- media is not available.
ACTIVE -- media is active.
LOCAL_HOLD -- media is put on-hold by local party.
REMOTE_HOLD -- media is put on-hold by remote party.
ERROR -- media error (e.g. ICE negotiation failure).
"""
- NONE = 0
+ NULL = 0
ACTIVE = 1
LOCAL_HOLD = 2
REMOTE_HOLD = 3
@@ -165,12 +165,12 @@ class MediaDir:
Member documentation:
- NONE -- media is not active
+ NULL -- media is not active
ENCODING -- media is active in transmit/encoding direction only.
DECODING -- media is active in receive/decoding direction only
ENCODING_DECODING -- media is active in both directions.
"""
- NONE = 0
+ NULL = 0
ENCODING = 1
DECODING = 2
ENCODING_DECODING = 3
@@ -189,6 +189,20 @@ class PresenceActivity:
AWAY = 1
BUSY = 2
+
+class SubscriptionState:
+ """Presence subscription state constants.
+
+ """
+ NULL = 0
+ SENT = 1
+ ACCEPTED = 2
+ PENDING = 3
+ ACTIVE = 4
+ TERMINATED = 5
+ UNKNOWN = 6
+
+
class TURNConnType:
"""These constants specifies the connection type to TURN server.
@@ -861,11 +875,38 @@ class AccountCallback:
Unless this callback is implemented, the default behavior is to
reject the call with default status code.
- Keyword arguments:
- call -- the new incoming call
+ Keyword arguments:
+ call -- the new incoming call
"""
call.hangup()
+ def on_incoming_subscribe(self, buddy, from_uri, pres_obj):
+ """Notification when incoming SUBSCRIBE request is received.
+
+ Application may use this callback to authorize the incoming
+ subscribe request (e.g. ask user permission if the request
+ should be granted)
+
+ Keyword arguments:
+ buddy -- The buddy object, if buddy is found. Otherwise
+ the value is None.
+ from_uri -- The URI string of the sender.
+ pres_obj -- Opaque presence subscription object, which is
+ needed by Account.pres_notify()
+
+ Return:
+ Tuple (code, reason), where:
+ code: The status code. If code is >= 300, the
+ request is rejected. If code is 200, the
+ request is accepted and NOTIFY will be sent
+ automatically. If code is 202, application
+ must accept or reject the request later with
+ Account.press_notify().
+ reason: Optional reason phrase, or None to use the
+ default reasoh phrase for the status code.
+ """
+ return (200, None)
+
def on_pager(self, from_uri, contact, mime_type, body):
"""
Notification that incoming instant message is received on
@@ -951,7 +992,7 @@ class Account:
lib -- the Lib instance.
id -- the pjsua account ID.
"""
- _cb = AccountCallback(self)
+ self._cb = AccountCallback(self)
self._id = id
self._lib = lib
self._lib._associate_account(self._id, self)
@@ -1101,6 +1142,19 @@ class Account:
self._lib._err_check("add_buddy()", self, err)
return Buddy(self._lib, buddy_id, self)
+ def pres_notify(self, pres_obj, state, reason="", hdr_list=None):
+ """Send NOTIFY to inform account presence status or to terminate
+ server side presence subscription.
+
+ Keyword arguments:
+ pres_obj -- The subscription object from on_incoming_subscribe()
+ callback
+ state -- Subscription state, from SubscriptionState
+ reason -- Optional reason phrase.
+ hdr_list -- Optional header list.
+ """
+ _pjsua.acc_pres_notify(self._id, pres_obj, state, reason,
+ Lib._create_msg_data(hdr_list))
class CallCallback:
"""Class to receive event notification from Call objects.
@@ -1275,8 +1329,8 @@ class CallInfo:
state_text = ""
last_code = 0
last_reason = ""
- media_state = MediaState.NONE
- media_dir = MediaDir.NONE
+ media_state = MediaState.NULL
+ media_dir = MediaDir.NULL
conf_slot = -1
call_time = 0
total_time = 0
@@ -1529,6 +1583,9 @@ class BuddyInfo:
activity -- the PresenceActivity
subscribed -- specify whether buddy's presence status is currently
being subscribed.
+ sub_state -- SubscriptionState
+ sub_term_reason -- The termination reason string of the last presence
+ subscription to this buddy, if any.
"""
uri = ""
contact = ""
@@ -1536,6 +1593,8 @@ class BuddyInfo:
online_text = ""
activity = PresenceActivity.UNKNOWN
subscribed = False
+ sub_state = SubscriptionState.NULL
+ sub_term_reason = ""
def __init__(self, pjsua_bi=None):
if pjsua_bi:
@@ -1548,6 +1607,8 @@ class BuddyInfo:
self.online_text = inf.status_text
self.activity = inf.activity
self.subscribed = inf.monitor_pres
+ self.sub_state = inf.sub_state
+ self.sub_term_reason = inf.sub_term_reason
class BuddyCallback:
@@ -1866,6 +1927,7 @@ class Lib:
py_ua_cfg.cb.on_call_replace_request = _cb_on_call_replace_request
py_ua_cfg.cb.on_call_replaced = _cb_on_call_replaced
py_ua_cfg.cb.on_reg_state = _cb_on_reg_state
+ py_ua_cfg.cb.on_incoming_subscribe = _cb_on_incoming_subscribe
py_ua_cfg.cb.on_buddy_state = _cb_on_buddy_state
py_ua_cfg.cb.on_pager = _cb_on_pager
py_ua_cfg.cb.on_pager_status = _cb_on_pager_status
@@ -2266,11 +2328,9 @@ class Lib:
self.buddy_by_uri[(uri.user, uri.host)] = buddy
def _lookup_buddy(self, buddy_id, uri=None):
- print "lookup_buddy, id=", buddy_id
buddy = self.buddy.has_key(buddy_id) and self.buddy[buddy_id] or None
if uri and not buddy:
sip_uri = SIPUri(uri)
- print "lookup_buddy, uri=", sip_uri.user, sip_uri.host
buddy = self.buddy_by_uri.has_key( (sip_uri.user, sip_uri.host) ) \
and self.buddy_by_uri[(sip_uri.user, sip_uri.host)] or \
None
@@ -2289,6 +2349,14 @@ class Lib:
if acc:
acc._cb.on_reg_state()
+ def _cb_on_incoming_subscribe(self, acc_id, buddy_id, from_uri, pres_obj):
+ acc = self._lookup_account(acc_id)
+ if acc:
+ buddy = self._lookup_buddy(buddy_id)
+ return acc._cb.on_incoming_subscribe(buddy, from_uri, pres_obj)
+ else:
+ return (404, None)
+
def _cb_on_incoming_call(self, acc_id, call_id, rdata):
acc = self._lookup_account(acc_id)
if acc:
@@ -2424,6 +2492,9 @@ def _cb_on_call_replaced(old_call_id, new_call_id):
def _cb_on_reg_state(acc_id):
_lib._cb_on_reg_state(acc_id)
+def _cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, pres):
+ return _lib._cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, pres)
+
def _cb_on_buddy_state(buddy_id):
_lib._cb_on_buddy_state(buddy_id)
diff --git a/pjsip-apps/src/python/samples/subscribe.py b/pjsip-apps/src/python/samples/subscribe.py
new file mode 100644
index 00000000..e48aeee8
--- /dev/null
+++ b/pjsip-apps/src/python/samples/subscribe.py
@@ -0,0 +1,94 @@
+# $Id$
+#
+# Authorization of incoming subscribe request
+#
+# Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+#
+import sys
+import pjsua as pj
+
+LOG_LEVEL = 3
+
+pending_pres = None
+
+def log_cb(level, str, len):
+ print str,
+
+class MyAccountCallback(pj.AccountCallback):
+ def __init__(self, account):
+ pj.AccountCallback.__init__(self, account)
+
+ def on_incoming_subscribe(self, buddy, from_uri, pres):
+ # Allow buddy to subscribe to our presence
+ global pending_pres
+
+ if buddy:
+ return (200, None)
+ print 'Incoming SUBSCRIBE request from', from_uri
+ print 'Press "A" to accept and add, "R" to reject the request'
+ pending_pres = pres
+ return (202, None)
+
+
+lib = pj.Lib()
+
+try:
+ # Init library with default config and some customized
+ # logging config.
+ lib.init(log_cfg = pj.LogConfig(level=LOG_LEVEL, callback=log_cb))
+
+ # Create UDP transport which listens to any available port
+ transport = lib.create_transport(pj.TransportType.UDP,
+ pj.TransportConfig(0))
+ print "\nListening on", transport.info().host,
+ print "port", transport.info().port, "\n"
+
+ # Start the library
+ lib.start()
+
+ # Create local account
+ acc = lib.create_account_for_transport(transport)
+ acc.set_callback(MyAccountCallback(acc))
+
+ my_sip_uri = "sip:" + transport.info().host + \
+ ":" + str(transport.info().port)
+
+ buddy = None
+
+ # Menu loop
+ while True:
+ print "My SIP URI is", my_sip_uri
+ print "Menu: t=toggle online status, q=quit"
+
+ input = sys.stdin.readline().rstrip("\r\n")
+
+ if input == "t":
+ acc.set_basic_status(not acc.info().online_status)
+
+ elif input == "A":
+ if pending_pres:
+ acc.pres_notify(pending_pres, pj.SubscriptionState.ACTIVE)
+ pending_pres = None
+ else:
+ print "No pending request"
+
+ elif input == "R":
+ if pending_pres:
+ acc.pres_notify(pending_pres, pj.SubscriptionState.TERMINATED,
+ "rejected")
+ pending_pres = None
+ else:
+ print "No pending request"
+
+ elif input == "q":
+ break
+
+ # Shutdown the library
+ lib.destroy()
+ lib = None
+
+except pj.Error, e:
+ print "Exception: " + str(e)
+ lib.destroy()
+ lib = None
+