diff options
Diffstat (limited to 'pjsip-apps/src/python')
-rw-r--r-- | pjsip-apps/src/python/_pjsua.c | 119 | ||||
-rw-r--r-- | pjsip-apps/src/python/_pjsua.h | 30 | ||||
-rw-r--r-- | pjsip-apps/src/python/pjsua.py | 93 | ||||
-rw-r--r-- | pjsip-apps/src/python/samples/subscribe.py | 94 |
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 + |