00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 #ifdef __HAVE_MMSSIP__
00034
00035 #ifdef PJSIP_AUTH_AUTO_SEND_NEXT
00036 #undef PJSIP_AUTH_AUTO_SEND_NEXT
00037 #endif
00038 #define PJSIP_AUTH_AUTO_SEND_NEXT 1
00039
00040 #ifdef PJSIP_AUTH_HEADER_CACHING
00041 #undef PJSIP_AUTH_HEADER_CACHING
00042 #endif
00043 #define PJSIP_AUTH_HEADER_CACHING 1
00044
00045
00046 #include "mmstools/tools.h"
00047 #include "mmstools/mmserror.h"
00048 #include "mmssip/mmssip.h"
00049
00050 static MMSSip *thiz = NULL;
00051 static bool registered = false;
00052 static pjsua_player_id ringtonePlayer = PJSUA_INVALID_ID;
00053 static pjsua_player_id busytonePlayer = PJSUA_INVALID_ID;
00054 static pjsua_player_id callingtonePlayer = PJSUA_INVALID_ID;
00055
00056 static void onIncomingCall(pjsua_acc_id, pjsua_call_id, pjsip_rx_data*);
00057 static void onCallState(pjsua_call_id, pjsip_event*);
00058 static void onCallMediaState(pjsua_call_id);
00059 static void onRegistrationState(pjsua_acc_id);
00060 static void onBuddyState(pjsua_buddy_id);
00061
00062 static void logCallback(int level, const char *data, int len) {
00063 DEBUGMSG("MMSSIP", data);
00064 }
00065
00066 MMSSip::MMSSip(const string &user,
00067 const string &passwd,
00068 const string ®istrar,
00069 const string &realm,
00070 const string &stunserver,
00071 const string &nameserver,
00072 const short int &localPort) :
00073 stunserver(stunserver),
00074 nameserver(nameserver),
00075 localPort(localPort),
00076 defaultAccount(-1) {
00077
00078
00079 if(thiz) {
00080 DEBUGMSG("MMSSIP", "There's already an instance of MMSSIP running.");
00081 throw MMSError(0, "There's already an instance of MMSSIP running.");
00082 }
00083
00084 thiz = this;
00085
00086 pj_status_t status;
00087
00088
00089 status = pjsua_create();
00090 if(status != PJ_SUCCESS) {
00091 DEBUGMSG("MMSSIP", "Error initializing SIP stack (pjsua_create)");
00092 throw MMSError(0, "Error initializing SIP stack (pjsua_create)");
00093 }
00094 DEBUGMSG("MMSSIP", "SIP stack init #1");
00095
00096
00097 pjsua_config cfg;
00098 pjsua_logging_config logCfg;
00099
00100 pjsua_config_default(&cfg);
00101 cfg.user_agent = pj_str((char*)"Disko SIP stack");
00102 if(stunserver != "") {
00103 DEBUGMSG("MMSSIP", "Using STUN server " + stunserver);
00104 cfg.stun_host = pj_str((char*)stunserver.c_str());
00105 }
00106 if(nameserver != "") {
00107 DEBUGMSG("MMSSIP", "Using nameserver " + nameserver);
00108 cfg.nameserver[0] = pj_str((char*)nameserver.c_str());
00109 }
00110 cfg.cb.on_incoming_call = &onIncomingCall;
00111 cfg.cb.on_call_media_state = &onCallMediaState;
00112 cfg.cb.on_call_state = &onCallState;
00113 cfg.cb.on_reg_state = &onRegistrationState;
00114 cfg.cb.on_buddy_state = &onBuddyState;
00115
00116 pjsua_logging_config_default(&logCfg);
00117 logCfg.level = 1;
00118 #ifdef __ENABLE_LOG__
00119 logCfg.console_level = 4;
00120 logCfg.cb = logCallback;
00121 #else
00122 logCfg.console_level = 0;
00123 #endif
00124
00125 status = pjsua_init(&cfg, &logCfg, NULL);
00126 if(status != PJ_SUCCESS) {
00127 DEBUGMSG("MMSSIP", "Error initializing SIP stack (pjsua_init)");
00128 throw MMSError(0, "Error initializing SIP stack (pjsua_init)");
00129 }
00130
00131 DEBUGMSG("MMSSIP", "SIP stack init #2");
00132
00133
00134 pjsua_transport_config transCfg;
00135
00136 pjsua_transport_config_default(&transCfg);
00137 transCfg.port = localPort;
00138 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &transCfg, NULL);
00139 if(status != PJ_SUCCESS) {
00140 DEBUGMSG("MMSSIP", "Error initializing SIP stack (pjsua_transport_create)");
00141 throw MMSError(0, "Error initializing SIP stack (pjsua_transport_create)");
00142 }
00143
00144 DEBUGMSG("MMSSIP", "UDP transport created");
00145
00146
00147 status = pjsua_start();
00148 if(status != PJ_SUCCESS) {
00149 DEBUGMSG("MMSSIP", "Error starting SIP stack (pjsua_start)");
00150 throw MMSError(0, "Error starting SIP stack (pjsua_start)");
00151 }
00152
00153 DEBUGMSG("MMSSIP", "SIP stack started");
00154
00155 if(user != "") {
00156 if(!this->registerAccount(user, passwd, registrar, realm, true)) {
00157 DEBUGMSG("MMSSIP", "Error registering account");
00158 throw MMSError(0, "Error registering account");
00159 }
00160 }
00161
00162 this->onCallSuccessfull = new sigc::signal<void, int, int>;
00163 this->onCallIncoming = new sigc::signal<void, int, string, int>;
00164 this->onCallDisconnected = new sigc::signal<void, int, int>;
00165 this->onCalling = new sigc::signal<void, int, int>;
00166 this->onBuddyStatus = new sigc::signal<void, MMSSipBuddy>;
00167 }
00168
00169 MMSSip::~MMSSip() {
00170 if(!pj_thread_is_registered()) {
00171 MMSSipThread tInfo;
00172 pj_bzero(tInfo.desc, sizeof(pj_thread_desc));
00173 if(pj_thread_register("MMSSIP", tInfo.desc, &tInfo.thread) == PJ_SUCCESS)
00174 pjsua_destroy();
00175 }
00176
00177 if(this->onCallSuccessfull) {
00178 this->onCallSuccessfull->clear();
00179 delete this->onCallSuccessfull;
00180 }
00181 if(this->onCallIncoming) {
00182 this->onCallIncoming->clear();
00183 delete this->onCallIncoming;
00184 }
00185 if(this->onCallDisconnected) {
00186 this->onCallDisconnected->clear();
00187 delete this->onCallDisconnected;
00188 }
00189 if(this->onCalling) {
00190 this->onCalling->clear();
00191 delete this->onCalling;
00192 }
00193 if(this->onBuddyStatus) {
00194 this->onBuddyStatus->clear();
00195 delete this->onBuddyStatus;
00196 }
00197 this->accounts.clear();
00198 this->buddies.clear();
00199 }
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216 const bool MMSSip::registerAccount(const string &user,
00217 const string &passwd,
00218 const string ®istrar,
00219 const string &realm,
00220 const bool defaultAcc,
00221 const bool autoanswer) {
00222 pj_status_t status;
00223 pjsua_acc_config accCfg;
00224 pjsua_acc_id accID;
00225 char tmpid[256], tmpreg[256];
00226
00227 DEBUGMSG("MMSSIP", "Registering account " + user + "@" + registrar +
00228 "(default: " + (defaultAcc ? "true" : "false") +
00229 ", autoanswer: " + (autoanswer ? "true" : "false") + ")");
00230
00231 snprintf(tmpid, sizeof(tmpid), "sip:%s@%s", user.c_str(), registrar.c_str());
00232 snprintf(tmpreg, sizeof(tmpreg), "sip:%s", realm.c_str());
00233
00234 pjsua_acc_config_default(&accCfg);
00235 accCfg.reg_timeout = 60;
00236 accCfg.id = pj_str(tmpid);
00237 accCfg.reg_uri = pj_str(tmpreg);
00238 if(defaultAcc) accCfg.priority += 1;
00239 accCfg.cred_count = 1;
00240 accCfg.cred_info[0].realm = pj_str((char*)"*");
00241 accCfg.cred_info[0].scheme = pj_str((char*)"Digest");
00242 accCfg.cred_info[0].username = pj_str((char*)user.c_str());
00243 accCfg.cred_info[0].data_type = 0;
00244 accCfg.cred_info[0].data = pj_str((char*)passwd.c_str());
00245 accCfg.publish_enabled = PJ_FALSE;
00246
00247 status = pjsua_acc_add(&accCfg, (defaultAcc ? PJ_TRUE : PJ_FALSE), &accID);
00248 if(status != PJ_SUCCESS) {
00249 DEBUGMSG("MMSSIP", "Error registering account sip:" + user + "@" + registrar + " (pjsua_acc_add)");
00250 return false;
00251 }
00252
00253 DEBUGMSG("MMSSIP", "Account " + user + "@" + registrar + " has ID " + iToStr(accID));
00254
00255 MMSSipAccount acc = {user, passwd, registrar, realm, autoanswer};
00256 this->accounts[accID] = acc;
00257 if(defaultAcc)
00258 this->defaultAccount = accID;
00259
00260 return true;
00261 }
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275 const int MMSSip::call(const string &user, const string &domain) {
00276 pj_status_t status;
00277 pj_str_t uri;
00278 pjsua_call_id call;
00279 char tmp[1024];
00280
00281 if(!registered) {
00282 DEBUGMSG("MMSSIP", "Cannot make a call (not registered)");
00283 throw MMSError(0, "Cannot make a call (not registered)");
00284 }
00285
00286 const char *cDomain;
00287 if((user.find("@") == string::npos) && this->defaultAccount >= 0) {
00288 cDomain = ((domain != "") ? domain.c_str() : this->accounts[defaultAccount].registrar.c_str());
00289 snprintf(tmp, 1024, "sip:%s@%s", user.c_str(), cDomain);
00290 }
00291 else
00292 snprintf(tmp, 1024, "sip:%s", user.c_str());
00293
00294 if(!pj_thread_is_registered()) {
00295 MMSSipThread tInfo;
00296 pj_bzero(tInfo.desc, sizeof(pj_thread_desc));
00297 if(pj_thread_register("MMSSIP", tInfo.desc, &tInfo.thread) == PJ_SUCCESS) {
00298 this->threadInfo.push_back(tInfo);
00299 } else {
00300 DEBUGMSG("MMSSIP", "Error registering thread (pj_thread_register)");
00301 throw MMSError(0, "Error registering thread (pj_thread_register)");
00302 }
00303 }
00304
00305 status = pjsua_verify_sip_url(tmp);
00306 if (status != PJ_SUCCESS) {
00307 DEBUGMSG("MMSSIP", "Invalid callee info sip:" + user + "@" + cDomain);
00308 throw MMSError(0, "Invalid callee info sip:" + user + "@" + cDomain);
00309 }
00310
00311 uri = pj_str(tmp);
00312 status = pjsua_call_make_call(this->defaultAccount, &uri, 0, NULL, NULL, &call);
00313 if (status != PJ_SUCCESS) {
00314 DEBUGMSG("MMSSIP", "Error calling sip:" + user + "@" + cDomain);
00315 char buf[1024];
00316 pj_strerror(status, buf, sizeof(buf));
00317 throw MMSError(0, buf);
00318 }
00319
00320 return call;
00321 }
00322
00323 void MMSSip::hangup(int id) {
00324 DEBUGMSG("MMSSIP", "calling pjsua_call_hangup (id=%d)", id);
00325
00326 if(!pj_thread_is_registered()) {
00327 MMSSipThread tInfo;
00328 pj_bzero(tInfo.desc, sizeof(pj_thread_desc));
00329 if(pj_thread_register("MMSSIP", tInfo.desc, &tInfo.thread) == PJ_SUCCESS) {
00330 this->threadInfo.push_back(tInfo);
00331 } else {
00332 DEBUGMSG("MMSSIP", "Error registering thread (pj_thread_register)");
00333 throw MMSError(0, "Error registering thread (pj_thread_register)");
00334 }
00335 }
00336
00337 if(id != PJSUA_INVALID_ID) {
00338
00339
00340 pjsua_call_info ci;
00341 pjsua_call_get_info(id, &ci);
00342 if(ci.state < PJSIP_INV_STATE_CONNECTING) {
00343 DEBUGMSG("MMSSIP", "answering with code 480");
00344 pjsua_call_answer(id, 480, NULL, NULL);
00345 }
00346 else
00347 pjsua_call_hangup(id, 0, NULL, NULL);
00348 }
00349 else
00350 pjsua_call_hangup_all();
00351 }
00352
00353 void MMSSip::answer(int id) {
00354 DEBUGMSG("MMSSIP", "calling pjsua_call_answer");
00355
00356 if(!pj_thread_is_registered()) {
00357 MMSSipThread tInfo;
00358 pj_bzero(tInfo.desc, sizeof(pj_thread_desc));
00359 if(pj_thread_register("MMSSIP", tInfo.desc, &tInfo.thread) == PJ_SUCCESS) {
00360 this->threadInfo.push_back(tInfo);
00361 } else {
00362 DEBUGMSG("MMSSIP", "Error registering thread (pj_thread_register)");
00363 throw MMSError(0, "Error registering thread (pj_thread_register)");
00364 }
00365 }
00366
00367 pjsua_call_answer(id, 200, NULL, NULL);
00368 }
00369
00370 void MMSSip::addBuddy(const string &name, const string &uri) {
00371 pjsua_buddy_config buddyCfg;
00372 pjsua_buddy_id buddyId;
00373 pjsua_buddy_info buddyInfo;
00374
00375 if(!registered) {
00376 DEBUGMSG("MMSSIP", "Cannot add buddy (not registered)");
00377 throw MMSError(0, "Cannot add buddy (not registered)");
00378 }
00379
00380 if(!pj_thread_is_registered()) {
00381 MMSSipThread tInfo;
00382 pj_bzero(tInfo.desc, sizeof(pj_thread_desc));
00383 if(pj_thread_register("MMSSIP", tInfo.desc, &tInfo.thread) == PJ_SUCCESS) {
00384 this->threadInfo.push_back(tInfo);
00385 } else {
00386 DEBUGMSG("MMSSIP", "Error registering thread (pj_thread_register)");
00387 throw MMSError(0, "Error registering thread (pj_thread_register)");
00388 }
00389 }
00390
00391 pjsua_buddy_config_default(&buddyCfg);
00392 char buri[80];
00393 sprintf(buri, "sip:%s", uri.c_str());
00394 buddyCfg.uri = pj_str(buri);
00395 buddyCfg.subscribe = true;
00396 if(pjsua_buddy_add(&buddyCfg, &buddyId) == PJ_SUCCESS) {
00397 DEBUGMSG("MMSSIP", "successfully added buddy " + name);
00398 MMSSipBuddy buddy = {name, uri, BUDDY_UNKNOWN};
00399 buddies[buddyId] = buddy;
00400 if(pjsua_buddy_get_info(buddyId, &buddyInfo) == PJ_SUCCESS) {
00401 buddy.status = (MMSSipBuddyStatus)buddyInfo.status;
00402 }
00403 buddies[buddyId] = buddy;
00404 onBuddyState(buddyId);
00405 }
00406 else
00407 DEBUGMSG("MMSSIP", "failed to add buddy " + name);
00408 }
00409
00410 MMSSipBuddy MMSSip::getBuddy(const int &id) {
00411 return this->buddies[id];
00412 }
00413
00414 bool MMSSip::setSpeakerVolume(const unsigned int percent) {
00415 if(percent > 100) return false;
00416 if(!pjsua_conf_adjust_tx_level(0, (float)percent / 100) == PJ_SUCCESS) {
00417 DEBUGMSG("MMSSIP", "setting speaker volume failed");
00418 return false;
00419 }
00420 DEBUGMSG("MMSSIP", "setting speaker volume to %d%%", percent);
00421
00422 return true;
00423 }
00424
00425 int MMSSip::getSpeakerVolume() {
00426 unsigned int tx_level, rx_level;
00427 if(pjsua_conf_get_signal_level(0, &tx_level, &rx_level) == PJ_SUCCESS)
00428 return tx_level;
00429
00430 return -1;
00431 }
00432
00433 bool MMSSip::getAutoAnswer(int accountId) {
00434 try {
00435 MMSSipAccount acc = this->accounts[accountId];
00436 return acc.autoanswer;
00437 }
00438 catch(std::exception& e) {
00439 throw MMSError(0, e.what());
00440 }
00441 }
00442
00443 void MMSSip::setAutoAnswer(int accountId, const bool value) {
00444 try {
00445 MMSSipAccount acc = this->accounts[accountId];
00446 acc.autoanswer = value;
00447 }
00448 catch(std::exception& e) {
00449 throw MMSError(0, e.what());
00450 }
00451 }
00452
00453
00454
00455
00456
00457
00458
00459
00460 bool MMSSip::registerRingtone(const string &filename) {
00461 pj_str_t tmp;
00462 if(pjsua_player_create(pj_cstr(&tmp, filename.c_str()), 0, &ringtonePlayer) == PJ_SUCCESS)
00463 return true;
00464
00465 return false;
00466 }
00467
00468
00469
00470
00471
00472
00473
00474
00475
00476
00477 bool MMSSip::registerBusytone(const string &filename) {
00478 pj_str_t tmp;
00479 if(pjsua_player_create(pj_cstr(&tmp, filename.c_str()), PJMEDIA_FILE_NO_LOOP, &busytonePlayer) == PJ_SUCCESS)
00480 return true;
00481
00482 return false;
00483 }
00484
00485
00486
00487
00488
00489
00490
00491
00492 bool MMSSip::registerCallingtone(const string &filename) {
00493 pj_str_t tmp;
00494 if(pjsua_player_create(pj_cstr(&tmp, filename.c_str()), 0, &callingtonePlayer) == PJ_SUCCESS)
00495 return true;
00496
00497 return false;
00498 }
00499
00500
00501 static void onIncomingCall(pjsua_acc_id accId,
00502 pjsua_call_id callId,
00503 pjsip_rx_data *rdata) {
00504 pjsua_call_info ci;
00505
00506 PJ_UNUSED_ARG(rdata);
00507
00508
00509 if(thiz->getAutoAnswer(accId)) {
00510 DEBUGMSG("MMSSIP", "Incoming call on account %d which has autoanswer feature turned on", accId);
00511 pjsua_call_id *ids;
00512 unsigned int count;
00513 if(pjsua_enum_calls(ids, &count) == PJ_SUCCESS) {
00514 for(unsigned int i = 0; i < count; ++i)
00515 pjsua_call_hangup(ids[i], 481, NULL, NULL);
00516 }
00517 thiz->answer(callId);
00518 } else {
00519 pjsua_call_get_info(callId, &ci);
00520
00521 DEBUGMSG("MMSSIP", "Incoming call from %.*s (id=%d)", (int)ci.remote_info.slen, ci.remote_info.ptr, callId);
00522
00523 if(thiz && thiz->onCallIncoming)
00524 thiz->onCallIncoming->emit(callId, ci.remote_info.ptr, ci.last_status);
00525 }
00526 }
00527
00528
00529 static void onCallState(pjsua_call_id callId, pjsip_event *e) {
00530 pjsua_call_info ci;
00531
00532 PJ_UNUSED_ARG(e);
00533
00534 pjsua_call_get_info(callId, &ci);
00535 DEBUGMSG("MMSSIP", "Call %d state=%d (%.*s)", callId, ci.state, (int)ci.state_text.slen, ci.state_text.ptr);
00536
00537 switch(ci.state) {
00538 case PJSIP_INV_STATE_NULL:
00539 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_NULL");
00540 break;
00541 case PJSIP_INV_STATE_CALLING:
00542 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_CALLING");
00543 if(thiz && thiz->onCalling)
00544 thiz->onCalling->emit(callId, ci.last_status);
00545 break;
00546 case PJSIP_INV_STATE_INCOMING:
00547 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_INCOMING");
00548 if(ringtonePlayer != PJSUA_INVALID_ID &&
00549 ci.role == PJSIP_ROLE_UAS &&
00550 ci.media_status == PJSUA_CALL_MEDIA_NONE)
00551 pjsua_conf_connect(pjsua_player_get_conf_port(ringtonePlayer), 0);
00552 break;
00553 case PJSIP_INV_STATE_EARLY:
00554 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_EARLY");
00555 break;
00556 case PJSIP_INV_STATE_CONNECTING:
00557 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_CONNECTING");
00558 if(callingtonePlayer != PJSUA_INVALID_ID &&
00559 ci.role == PJSIP_ROLE_UAS &&
00560 ci.media_status == PJSUA_CALL_MEDIA_NONE)
00561 pjsua_conf_connect(pjsua_player_get_conf_port(callingtonePlayer), 0);
00562 break;
00563 case PJSIP_INV_STATE_CONFIRMED:
00564 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_CONFIRMED");
00565 if(callingtonePlayer != PJSUA_INVALID_ID)
00566 pjsua_conf_disconnect(pjsua_player_get_conf_port(callingtonePlayer), 0);
00567 if(thiz && thiz->onCallSuccessfull)
00568 thiz->onCallSuccessfull->emit(callId, ci.last_status);
00569 break;
00570 case PJSIP_INV_STATE_DISCONNECTED:
00571 DEBUGMSG("MMSSIP", "lastStatusText: %s", ci.last_status_text);
00572 DEBUGMSG("MMSSIP", "onCallState: PJSIP_INV_STATE_DISCONNECTED");
00573 if(ringtonePlayer != PJSUA_INVALID_ID)
00574 pjsua_conf_disconnect(pjsua_player_get_conf_port(ringtonePlayer), 0);
00575 if((ci.last_status == 486) && (busytonePlayer != PJSUA_INVALID_ID))
00576 pjsua_conf_connect(pjsua_player_get_conf_port(busytonePlayer), 0);
00577 if(thiz && thiz->onCallDisconnected)
00578 thiz->onCallDisconnected->emit(callId, ci.last_status);
00579 break;
00580 default:
00581
00582 break;
00583 }
00584 }
00585
00586
00587 static void onCallMediaState(pjsua_call_id callId) {
00588 pjsua_call_info ci;
00589
00590 if(ringtonePlayer != PJSUA_INVALID_ID)
00591 pjsua_conf_disconnect(pjsua_player_get_conf_port(ringtonePlayer), 0);
00592
00593 pjsua_call_get_info(callId, &ci);
00594
00595 if(ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
00596
00597 pjsua_conf_connect(ci.conf_slot, 0);
00598 pjsua_conf_connect(0, ci.conf_slot);
00599 }
00600 }
00601
00602 static void onRegistrationState(pjsua_acc_id id) {
00603 pjsua_acc_info info;
00604
00605 if(pjsua_acc_get_info(id, &info) == PJ_SUCCESS) {
00606 if(info.status == 200 && info.is_default) {
00607 registered = true;
00608 DEBUGMSG("MMSSIP", (registered ? "registered" : "not registered"));
00609 }
00610 DEBUGMSG("MMSSIP", "account: %s", info.acc_uri);
00611 DEBUGMSG("MMSSIP", "status: %d", info.status);
00612 DEBUGMSG("MMSSIP", "status_text: %s", info.status_text);
00613 DEBUGMSG("MMSSIP", "online_status: %d", info.online_status);
00614 DEBUGMSG("MMSSIP", "online_status_text: %s", info.online_status_text);
00615
00616
00617 if(registered && !info.online_status) {
00618 if(pjsua_acc_set_online_status(id, PJ_TRUE) == PJ_SUCCESS)
00619 DEBUGMSG("MMSSIP", "Setting online status successfull");
00620 else
00621 DEBUGMSG("MMSSIP", "Setting online status failed");
00622 }
00623 }
00624 }
00625
00626 static void onBuddyState(pjsua_buddy_id id) {
00627 pjsua_buddy_info info;
00628
00629 if(pjsua_buddy_get_info(id, &info) == PJ_SUCCESS) {
00630 if(thiz && thiz->onBuddyStatus)
00631 thiz->onBuddyStatus->emit(thiz->getBuddy(id));
00632 }
00633 }
00634
00635 #endif