yate/engine/Client.cpp

6267 lines
175 KiB
C++

/**
* Client.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2014 Null Team
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* 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.
*/
#include "yatecbase.h"
#include <stdio.h>
#include <stdarg.h>
#ifndef OUT_BUFFER_SIZE
#define OUT_BUFFER_SIZE 8192
#endif
using namespace TelEngine;
class ClientThreadProxy
{
public:
enum {
setVisible,
openPopup,
hasElement,
setShow,
setText,
setActive,
setFocus,
setCheck,
setSelect,
setUrgent,
hasOption,
addOption,
delOption,
getOptions,
addTableRow,
setMultipleRows,
insertTableRow,
delTableRow,
setTableRow,
getTableRow,
updateTableRow,
updateTableRows,
clearTable,
setBusy,
getText,
getCheck,
getSelect,
createWindow,
closeWindow,
createDialog,
closeDialog,
setParams,
addLines,
createObject,
buildMenu,
removeMenu,
setImage,
setImageFit,
setProperty,
getProperty,
openUrl
};
ClientThreadProxy(int func, const String& name, bool show, Window* wnd = 0, Window* skip = 0);
ClientThreadProxy(int func, const String& name, bool show, bool activate,
Window* wnd = 0, Window* skip = 0);
ClientThreadProxy(int func, const String& name, const String& text, Window* wnd, Window* skip);
ClientThreadProxy(int func, const String& name, const String& text,
const String& item, bool show, Window* wnd, Window* skip);
ClientThreadProxy(int func, const String& name, String* rtext, bool* rbool,
Window* wnd, Window* skip);
ClientThreadProxy(int func, const String& name, const NamedList* params,
const Window* parent);
ClientThreadProxy(int func, const String& name, const String& item, bool start,
const NamedList* params, Window* wnd, Window* skip);
ClientThreadProxy(int func, const String& name, const NamedList* params,
Window* wnd, Window* skip);
ClientThreadProxy(int func, const String& name, const NamedList* params,
unsigned int uintVal, bool atStart, Window* wnd, Window* skip);
ClientThreadProxy(int func, void** addr, const String& name, const String& text, const NamedList* params);
ClientThreadProxy(int func, const String& name, const String& text,
const String& item, const NamedList* params, Window* wnd, Window* skip);
void process();
bool execute();
private:
int m_func;
bool m_rval;
String m_name;
String m_text;
String m_item;
bool m_bool;
String* m_rtext;
bool* m_rbool;
Window* m_wnd;
Window* m_skip;
const NamedList* m_params;
unsigned int m_uint;
void** m_pointer;
};
// holder for a postponed message
class PostponedMessage : public Message
{
public:
inline PostponedMessage(const Message& msg, int id, bool copyUserData)
: Message(msg), m_id(id) {
if (copyUserData)
userData(msg.userData());
}
inline int id() const
{ return m_id; }
private:
int m_id;
};
// engine.start message handler used to notify logics
class EngineStartHandler : public MessageHandler
{
public:
inline EngineStartHandler(unsigned int prio = 100)
: MessageHandler("engine.start",prio)
{}
virtual bool received(Message& msg) {
Client::s_engineStarted = true;
if (!(Client::self() && Client::self()->postpone(msg,Client::EngineStart)))
Debug(DebugGoOn,"Failed to postpone %s in client",msg.c_str());
return false;
}
};
// System tray icon definition.
// The NamedPointer keeps the icon parameter list in its data
class TrayIconDef : public NamedPointer
{
YNOCOPY(TrayIconDef); // No automatic copies please
public:
inline TrayIconDef(int prio, NamedList* params)
: NamedPointer(params ? params->c_str() : "",params),
m_priority(prio)
{}
int m_priority;
private:
TrayIconDef() : NamedPointer("") {} // No default constructor
};
namespace { // anonymous
/**
* Helper class providing a thread on which the Client to run on
* A thread for the client
*/
class ClientThread : public Thread
{
public:
/**
* Constructor
* @param name Static name of the thread (for debugging purpose only)
* @param prio Thread priority
*/
ClientThread(Client* client, Priority prio = Normal)
: Thread("Client",prio),
m_client(client)
{ }
/**
* Destructor
*/
inline ~ClientThread()
{}
/**
* Run thread, inherited from Thread
*/
virtual void run()
{
m_client->run();
}
/**
* Clean up, inherited from Thread
*/
virtual void cleanup()
{
m_client->cleanup();
m_client->setThread(0);
}
private:
Client* m_client;
};
}; // anonymous namespace
/**
* Static classes/function/data
*/
// Struct used to build client relays array
struct MsgRelay
{
const char* name;
int id;
int prio;
};
// List of window params prefix handled in setParams()
static const String s_wndParamPrefix[] = {"show:","active:","focus:","check:","select:","display:",""};
// Error messages returned by channels
static String s_userBusy = "User busy";
static String s_rejectReason = "Rejected";
static String s_hangupReason = "User hangup";
static String s_cancelReason = "Cancelled";
static unsigned int s_eventLen = 0; // Log maximum lines (0: unlimited)
static Mutex s_debugMutex(false,"ClientDebug");
static Mutex s_proxyMutex(false,"ClientProxy");
static Mutex s_postponeMutex(false,"ClientPostpone");
static ObjList s_postponed;
static NamedList* s_debugLog = 0;
static ClientThreadProxy* s_proxy = 0;
static bool s_busy = false;
static String s_incomingUrlParam; // Incoming URL param in call.execute message
Client* Client::s_client = 0;
Configuration Client::s_settings; // Client settings
Configuration Client::s_actions; // Logic preferrences
Configuration Client::s_accounts; // Accounts
Configuration Client::s_contacts; // Contacts
Configuration Client::s_providers; // Provider settings
Configuration Client::s_history; // Call log
Configuration Client::s_calltoHistory; // Dialed destinations history
int Client::s_changing = 0;
Regexp Client::s_notSelected("^-\\(.*\\)-$"); // Holds a not selected/set value match
Regexp Client::s_guidRegexp("^\\([[:xdigit:]]\\{8\\}\\)-\\(\\([[:xdigit:]]\\{4\\}\\)-\\)\\{3\\}\\([[:xdigit:]]\\{12\\}\\)$");
ObjList Client::s_logics;
String Client::s_skinPath; // Skin path
String Client::s_soundPath; // Sounds path
String Client::s_ringInName = "defaultringin"; // Ring name for incoming channels
String Client::s_ringOutName = "defaultringout"; // Ring name for outgoing channels
String Client::s_statusWidget = "status"; // Status widget's name
String Client::s_debugWidget = "log_events"; // Default widget displaying the debug text
// The list of client's toggles
String Client::s_toggles[OptCount] = {
"multilines", "autoanswer", "ringincoming", "ringoutgoing",
"activatelastoutcall", "activatelastincall", "activatecallonselect",
"display_keypad", "openincomingurl", "addaccountonstartup",
"dockedchat", "destroychat", "notifychatstate", "showemptychat",
"sendemptychat"
};
int Client::s_maxConfPeers = 50;
bool Client::s_engineStarted = false; // Engine started flag
bool Client::s_idleLogicsTick = false; // Call logics' timerTick()
bool Client::s_exiting = false; // Client exiting flag
ClientDriver* ClientDriver::s_driver = 0;
String ClientDriver::s_confName = "conf/client"; // The name of the client's conference room
bool ClientDriver::s_dropConfPeer = true; // Drop a channel's old peer when terminated while in conference
String ClientDriver::s_device; // Currently used audio device
ObjList ClientSound::s_sounds; // ClientSound's list
Mutex ClientSound::s_soundsMutex(true,"ClientSound"); // ClientSound's list lock mutex
String ClientSound::s_calltoPrefix = "wave/play/"; // Client sound target prefix
static NamedList s_trayIcons(""); // Tray icon stacks. This list is managed in the client's thread
// Each item is a NamedPointer whose name is the window name
// and with ObjList data containing item defs
// Client relays
static const MsgRelay s_relays[] = {
{"call.cdr", Client::CallCdr, 90},
{"ui.action", Client::UiAction, 150},
{"user.login", Client::UserLogin, 50},
{"user.notify", Client::UserNotify, 50},
{"resource.notify", Client::ResourceNotify, 50},
{"resource.subscribe", Client::ResourceSubscribe, 50},
{"clientchan.update", Client::ClientChanUpdate, 50},
{"user.roster", Client::UserRoster, 50},
{"contact.info", Client::ContactInfo, 50},
{0,0,0},
};
// Channel slave type
const TokenDict ClientChannel::s_slaveTypes[] = {
{"conference", ClientChannel::SlaveConference},
{"transfer", ClientChannel::SlaveTransfer},
{0,0}
};
// Channel notifications
const TokenDict ClientChannel::s_notification[] = {
{"startup", ClientChannel::Startup},
{"destroyed", ClientChannel::Destroyed},
{"active", ClientChannel::Active},
{"onhold", ClientChannel::OnHold},
{"noticed", ClientChannel::Noticed},
{"addresschanged", ClientChannel::AddrChanged},
{"routed", ClientChannel::Routed},
{"accepted", ClientChannel::Accepted},
{"rejected", ClientChannel::Rejected},
{"progressing", ClientChannel::Progressing},
{"ringing", ClientChannel::Ringing},
{"answered", ClientChannel::Answered},
{"transfer", ClientChannel::Transfer},
{"conference", ClientChannel::Conference},
{"audioset", ClientChannel::AudioSet},
{0,0}
};
// Resource status names
const TokenDict ClientResource::s_statusName[] = {
{"offline", ClientResource::Offline},
{"connecting",ClientResource::Connecting},
{"online", ClientResource::Online},
{"busy", ClientResource::Busy},
{"dnd", ClientResource::Dnd},
{"away", ClientResource::Away},
{"xa", ClientResource::Xa},
{0,0}
};
// resource.notify capability names
const TokenDict ClientResource::s_resNotifyCaps[] = {
{"audio", ClientResource::CapAudio},
{"filetransfer", ClientResource::CapFileTransfer},
{"fileinfoshare", ClientResource::CapFileInfo},
{"resultsetmngt", ClientResource::CapRsm},
{0,0}
};
// MucRoomMember affiliations
const TokenDict MucRoomMember::s_affName[] = {
{"owner", MucRoomMember::Owner},
{"admin", MucRoomMember::Admin},
{"member", MucRoomMember::Member},
{"outcast", MucRoomMember::Outcast},
{"none", MucRoomMember::AffNone},
{0,0}
};
// MucRoomMember roles
const TokenDict MucRoomMember::s_roleName[] = {
{"moderator", MucRoomMember::Moderator},
{"participant", MucRoomMember::Participant},
{"visitor", MucRoomMember::Visitor},
{"none", MucRoomMember::RoleNone},
{0,0}
};
String ClientContact::s_chatPrefix = "chat"; // Client contact chat window prefix
String ClientContact::s_dockedChatWnd= "dockedchat"; // Docked chat window name
String ClientContact::s_dockedChatWidget = "dockedchatwidget"; // Docked chat widget name
String ClientContact::s_mucsWnd = "mucs"; // MUC rooms window name
String ClientContact::s_chatInput = "message"; // Chat input widget name
// Debug output handler
static void dbg_client_func(const char* buf, int level)
{
if (!buf)
return;
Lock lock(s_debugMutex);
if (!s_debugLog)
s_debugLog = new NamedList("");
s_debugLog->addParam(buf,String(level));
}
// Utility function used in Client::action()
// Output a debug message and calls a logic's action method
static inline bool callLogicAction(ClientLogic* logic, Window* wnd, const String& name, NamedList* params)
{
if (!logic)
return false;
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) action='%s' in window (%p,%s) [%p]",
logic->toString().c_str(),name.c_str(),wnd,wnd ? wnd->id().c_str() : "",logic);
return logic->action(wnd,name,params);
}
// Utility function used in Client::toggle()
// Output a debug message and calls a logic's toggle method
static inline bool callLogicToggle(ClientLogic* logic, Window* wnd, const String& name, bool active)
{
if (!logic)
return false;
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) toggle='%s' active=%s in window (%p,%s) [%p]",
logic->toString().c_str(),name.c_str(),String::boolText(active),
wnd,wnd ? wnd->id().c_str() : "",logic);
return logic->toggle(wnd,name,active);
}
// Utility function used in Client::select()
// Output a debug message and calls a logic's select method
static inline bool callLogicSelect(ClientLogic* logic, Window* wnd, const String& name,
const String& item, const String& text, const NamedList* items)
{
if (!logic)
return false;
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) select='%s' item='%s' items=%p in window (%p,%s) [%p]",
logic->toString().c_str(),name.c_str(),item.c_str(),items,
wnd,wnd ? wnd->id().c_str() : "",logic);
if (!items)
return logic->select(wnd,name,item,text);
return logic->select(wnd,name,*items);
}
// Utility function used to check for action/toggle/select preferences
// Check for a substitute
// Check if only a logic should process the action
// Check for a preffered logic to process the action
// Check if a logic should be ignored (not notified)
// Otherwise: check if the action should be ignored
static inline bool hasOverride(const NamedList* params, String& name, String& handle,
bool& only, bool& prefer, bool& ignore, bool& bailout)
{
static const String s_ignoreString = "ignore";
if (!params)
return false;
handle = params->getValue(name);
// Set name if a substitute is found
if (handle.startSkip("sameas:",false)) {
const char* tmp = params->getValue(handle);
if (tmp) {
name = handle;
handle = tmp;
}
else
handle = "";
}
// Check logic indications
if (!handle)
return false;
only = handle.startSkip("only:",false);
if (only)
return true;
prefer = handle.startSkip("prefer:",false);
ignore = !prefer && handle.startSkip("ignore:",false);
bailout = !ignore && handle == s_ignoreString;
return true;
}
// Utility: request to client to flash a widget's item (page, list item ...)
static inline void flashItem(bool on, const String& name, const String& item,
Window* w)
{
if (!Client::self())
return;
Client::self()->setProperty(name,"_yate_flashitem",
String(on) + ":" + item,w);
}
/**
* Window
*/
// Constructor with the specified id
Window::Window(const char* id)
: m_id(id), m_visible(false), m_active(false), m_master(false), m_popup(false),
m_saveOnClose(true), m_populated(false), m_initialized(false)
{
DDebug(ClientDriver::self(),DebugAll,"Window(%s) created",m_id.c_str());
}
// destructor
Window::~Window()
{
if (Client::self())
Client::self()->m_windows.remove(this,false);
DDebug(ClientDriver::self(),DebugAll,"Window(%s) destroyed",m_id.c_str());
}
// retrieve the window id
const String& Window::toString() const
{
return m_id;
}
// set the window title
void Window::title(const String& text)
{
m_title = text;
}
// set the window context
void Window::context(const String& text)
{
m_context = text;
}
// checkes if this window is related to the given window
bool Window::related(const Window* wnd) const
{
if ((wnd == this) || !wnd || wnd->master())
return false;
return true;
}
// function for interpreting a set or parameters and take appropiate action
// maybe not needed anymore?
bool Window::setParams(const NamedList& params)
{
bool ok = true;
NamedIterator iter(params);
for (const NamedString* s = 0; 0 != (s = iter.get());) {
String n(s->name());
if (n == YSTRING("title"))
title(*s);
if (n == YSTRING("context"))
context(*s);
else if (n.startSkip("show:",false) || n.startSkip("display:",false))
ok = setShow(n,s->toBoolean()) && ok;
else if (n.startSkip("active:",false))
ok = setActive(n,s->toBoolean()) && ok;
else if (n.startSkip("focus:",false))
ok = setFocus(n,s->toBoolean()) && ok;
else if (n.startSkip("check:",false))
ok = setCheck(n,s->toBoolean()) && ok;
else if (n.startSkip("select:",false))
ok = setSelect(n,*s) && ok;
else if (n.startSkip("image:",false))
ok = setImage(n,*s) && ok;
else if (n.startSkip("property:",false)) {
// Set property: object_name:property_name=value
int pos = n.find(':');
if (pos > 0)
ok = setProperty(n.substr(0,pos),n.substr(pos + 1),*s) && ok;
else
ok = false;
}
else if (n.find(':') < 0)
ok = setText(n,*s) && ok;
else
ok = false;
}
return ok;
}
// Append or insert text lines to a widget
bool Window::addLines(const String& name, const NamedList* lines, unsigned int max,
bool atStart)
{
DDebug(ClientDriver::self(),DebugInfo,"stub addLines('%s',%p,%u,%s) [%p]",
name.c_str(),lines,max,String::boolText(atStart),this);
return false;
}
// stub function for adding a row to a table
bool Window::addTableRow(const String& name, const String& item, const NamedList* data, bool atStart)
{
DDebug(ClientDriver::self(),DebugInfo,"stub addTableRow('%s','%s',%p,%s) [%p]",
name.c_str(),item.c_str(),data,String::boolText(atStart),this);
return false;
}
// stub function for setting multiple lines at once
bool Window::setMultipleRows(const String& name, const NamedList& data, const String& prefix)
{
DDebug(ClientDriver::self(),DebugInfo,"stub setMultipleRows('%s',%p,'%s') [%p]",
name.c_str(),&data,prefix.c_str(),this);
return false;
}
// stub function for inserting a row to a table
bool Window::insertTableRow(const String& name, const String& item,
const String& before, const NamedList* data)
{
DDebug(ClientDriver::self(),DebugInfo,"stub inserTableRow('%s','%s','%s',%p) [%p]",
name.c_str(),item.c_str(),before.c_str(),data,this);
return false;
}
// stub function for deleting a row from a table
bool Window::delTableRow(const String& name, const String& item)
{
DDebug(ClientDriver::self(),DebugInfo,"stub delTableRow('%s','%s') [%p]",
name.c_str(),item.c_str(),this);
return false;
}
// stub function for setting the value for a row
bool Window::setTableRow(const String& name, const String& item, const NamedList* data)
{
DDebug(ClientDriver::self(),DebugInfo,"stub setTableRow('%s','%s',%p) [%p]",
name.c_str(),item.c_str(),data,this);
return false;
}
// Set a table row or add a new one if not found
bool Window::updateTableRow(const String& name, const String& item,
const NamedList* data, bool atStart)
{
DDebug(ClientDriver::self(),DebugInfo,"stub updateTableRow('%s','%s',%p,%s) [%p]",
name.c_str(),item.c_str(),data,String::boolText(atStart),this);
return false;
}
// Add or set one or more table row(s). Screen update is locked while changing the table.
// Each data list element is a NamedPointer carrying a NamedList with item parameters.
// The name of an element is the item to update. Set element's value to 'false'
// to avoid adding a new item.
bool Window::updateTableRows(const String& name, const NamedList* data, bool atStart)
{
DDebug(ClientDriver::self(),DebugInfo,"stub updateTableRows('%s',%p,%s) [%p]",
name.c_str(),data,String::boolText(atStart),this);
return false;
}
// stub function for retrieving the information from a row
bool Window::getTableRow(const String& name, const String& item, NamedList* data)
{
DDebug(ClientDriver::self(),DebugInfo,"stub getTableRow('%s','%s',%p) [%p]",
name.c_str(),item.c_str(),data,this);
return false;
}
// stub function for clearing a table
bool Window::clearTable(const String& name)
{
DDebug(ClientDriver::self(),DebugInfo,"stub clearTable('%s') [%p]",
name.c_str(),this);
return false;
}
// Check window param prefix
bool Window::isValidParamPrefix(const String& prefix)
{
for (int i = 0; s_wndParamPrefix[i].length(); i++)
if (prefix.startsWith(s_wndParamPrefix[i]))
return prefix.length() > s_wndParamPrefix[i].length();
return false;
}
/**
* UIFactory
*/
ObjList UIFactory::s_factories;
// Constructor. Append itself to the factories list
UIFactory::UIFactory(const char* name)
: String(name)
{
s_factories.append(this)->setDelete(false);
Debug(ClientDriver::self(),DebugAll,"Added factory '%s' [%p]",name,this);
}
// Destructor
UIFactory::~UIFactory()
{
s_factories.remove(this,false);
Debug(ClientDriver::self(),DebugAll,"Removed factory '%s' [%p]",c_str(),this);
}
// Ask all factories to create an object of a given type
void* UIFactory::build(const String& type, const char* name, NamedList* params,
const char* factory)
{
for (ObjList* o = s_factories.skipNull(); o; o = o->skipNext()) {
UIFactory* f = static_cast<UIFactory*>(o->get());
if (!f->canBuild(type) || (factory && *f != factory))
continue;
DDebug(ClientDriver::self(),DebugAll,
"Factory '%s' trying to create type='%s' name='%s' [%p]",
f->c_str(),type.c_str(),name,f);
void* p = f->create(type,name,params);
if (p)
return p;
}
return 0;
}
/**
* ClientThreadProxy
*/
ClientThreadProxy::ClientThreadProxy(int func, const String& name, bool show,
Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_bool(show), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(0), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, bool show,
bool activate, Window* wnd, Window* skip)
: m_func(func), m_rval(activate),
m_name(name), m_bool(show), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(0), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const String& text,
Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_text(text), m_bool(false), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(0), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const String& text,
const String& item, bool show, Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_text(text), m_item(item), m_bool(show), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(0), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, String* rtext,
bool* rbool, Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_bool(false), m_rtext(rtext), m_rbool(rbool),
m_wnd(wnd), m_skip(skip), m_params(0), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const NamedList* params,
const Window* parent)
: m_func(func), m_rval(false),
m_name(name), m_bool(false), m_rtext(0), m_rbool(0),
m_wnd(const_cast<Window*>(parent)), m_skip(0), m_params(params), m_uint(0),
m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const String& item,
bool start, const NamedList* params, Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_item(item), m_bool(start), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(params), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const NamedList* params,
Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_bool(false), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(params), m_uint(0), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const NamedList* params,
unsigned int uintVal, bool atStart, Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_bool(atStart), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(params), m_uint(uintVal), m_pointer(0)
{
}
ClientThreadProxy::ClientThreadProxy(int func, void** addr, const String& name,
const String& text, const NamedList* params)
: m_func(func), m_rval(false),
m_name(name), m_text(text), m_bool(false), m_rtext(0), m_rbool(0),
m_wnd(0), m_skip(0), m_params(params), m_uint(0), m_pointer(addr)
{
}
ClientThreadProxy::ClientThreadProxy(int func, const String& name, const String& text,
const String& item, const NamedList* params, Window* wnd, Window* skip)
: m_func(func), m_rval(false),
m_name(name), m_text(text), m_item(item), m_bool(false), m_rtext(0), m_rbool(0),
m_wnd(wnd), m_skip(skip), m_params(params), m_uint(0), m_pointer(0)
{
}
void ClientThreadProxy::process()
{
XDebug(DebugAll,"ClientThreadProxy::process() %d [%p]",m_func,this);
Client* client = Client::self();
if (!client || Client::exiting()) {
s_busy = false;
return;
}
switch (m_func) {
case setVisible:
m_rval = Client::setVisible(m_name,m_bool,m_rval);
break;
case openPopup:
m_rval = Client::openPopup(m_name,m_params,m_wnd);
break;
case hasElement:
m_rval = client->hasElement(m_name,m_wnd,m_skip);
break;
case setShow:
m_rval = client->setShow(m_name,m_bool,m_wnd,m_skip);
break;
case setText:
m_rval = client->setText(m_name,m_text,m_bool,m_wnd,m_skip);
break;
case setActive:
m_rval = client->setActive(m_name,m_bool,m_wnd,m_skip);
break;
case setFocus:
m_rval = client->setFocus(m_name,m_bool,m_wnd,m_skip);
break;
case setCheck:
m_rval = client->setCheck(m_name,m_bool,m_wnd,m_skip);
break;
case setSelect:
m_rval = client->setSelect(m_name,m_text,m_wnd,m_skip);
break;
case setUrgent:
m_rval = client->setUrgent(m_name,m_bool,m_wnd,m_skip);
break;
case hasOption:
m_rval = client->hasOption(m_name,m_text,m_wnd,m_skip);
break;
case addOption:
m_rval = client->addOption(m_name,m_item,m_bool,m_text,m_wnd,m_skip);
break;
case delOption:
m_rval = client->delOption(m_name,m_text,m_wnd,m_skip);
break;
case addTableRow:
m_rval = client->addTableRow(m_name,m_item,m_params,m_bool,m_wnd,m_skip);
break;
case setMultipleRows:
m_rval = client->setMultipleRows(m_name,*m_params,m_item,m_wnd,m_skip);
break;
case insertTableRow:
m_rval = client->insertTableRow(m_name,m_item,m_text,m_params,m_wnd,m_skip);
break;
case delTableRow:
m_rval = client->delTableRow(m_name,m_text,m_wnd,m_skip);
break;
case setTableRow:
m_rval = client->setTableRow(m_name,m_item,m_params,m_wnd,m_skip);
break;
case updateTableRow:
m_rval = client->updateTableRow(m_name,m_item,m_params,m_bool,m_wnd,m_skip);
break;
case updateTableRows:
m_rval = client->updateTableRows(m_name,m_params,m_bool,m_wnd,m_skip);
break;
case getTableRow:
m_rval = client->getTableRow(m_name,m_item,const_cast<NamedList*>(m_params),m_wnd,m_skip);
break;
case clearTable:
m_rval = client->clearTable(m_name,m_wnd,m_skip);
break;
case setBusy:
m_rval = client->setBusy(m_name,m_bool,m_wnd,m_skip);
break;
case getText:
m_rval = client->getText(m_name,*m_rtext,m_rbool ? *m_rbool : false,m_wnd,m_skip);
break;
case getCheck:
m_rval = client->getCheck(m_name,*m_rbool,m_wnd,m_skip);
break;
case getSelect:
if (!m_params)
m_rval = client->getSelect(m_name,*m_rtext,m_wnd,m_skip);
else
m_rval = client->getSelect(m_name,*const_cast<NamedList*>(m_params),m_wnd,m_skip);
break;
case getOptions:
m_rval = client->getOptions(m_name,const_cast<NamedList*>(m_params),m_wnd,m_skip);
break;
case createWindow:
m_rval = client->createWindowSafe(m_name,m_text);
break;
case closeWindow:
m_rval = client->closeWindow(m_name,m_bool);
break;
case createDialog:
m_rval = client->createDialog(m_name,m_wnd,m_text,const_cast<NamedList*>(m_params));
break;
case closeDialog:
m_rval = client->closeDialog(m_name,m_wnd,m_skip);
break;
case setParams:
m_rval = client->setParams(m_params,m_wnd,m_skip);
break;
case addLines:
m_rval = client->addLines(m_name,m_params,m_uint,
m_bool,m_wnd,m_skip);
break;
case createObject:
m_rval = client->createObject(m_pointer,m_name,m_text,const_cast<NamedList*>(m_params));
break;
case buildMenu:
m_rval = m_params && client->buildMenu(*m_params,m_wnd,m_skip);
break;
case removeMenu:
m_rval = m_params && client->removeMenu(*m_params,m_wnd,m_skip);
break;
case setImage:
m_rval = client->setImage(m_name,m_text,m_wnd,m_skip);
break;
case setImageFit:
m_rval = client->setImageFit(m_name,m_text,m_wnd,m_skip);
break;
case setProperty:
m_rval = client->setProperty(m_name,m_item,m_text,m_wnd,m_skip);
break;
case getProperty:
m_rval = client->getProperty(m_name,m_item,m_text,m_wnd,m_skip);
break;
case openUrl:
m_rval = client->openUrl(m_name);
break;
}
s_busy = false;
}
bool ClientThreadProxy::execute()
{
XDebug(DebugAll,"ClientThreadProxy::execute() %d in %p [%p]",
m_func,Thread::current(),this);
s_proxyMutex.lock();
s_proxy = this;
s_busy = true;
while (s_busy)
Thread::yield();
s_proxyMutex.unlock();
return m_rval;
}
/**
* Client
*/
// Constructor
Client::Client(const char *name)
: m_initialized(false), m_line(0), m_oneThread(true),
m_defaultLogic(0), m_clientThread(0)
{
// Set default options
for (unsigned int i = 0; i < OptCount; i++)
m_toggles[i] = false;
m_toggles[OptMultiLines] = true;
m_toggles[OptKeypadVisible] = true;
m_toggles[OptAddAccountOnStartup] = true;
m_toggles[OptNotifyChatState] = true;
m_toggles[OptDockedChat] = true;
m_toggles[OptRingIn] = true;
m_toggles[OptRingOut] = true;
s_incomingUrlParam = Engine::config().getValue("client","incomingcallurlparam",
"caller_info_uri");
// Install relays
for (int i = 0; s_relays[i].name; i++)
installRelay(s_relays[i].name,s_relays[i].id,s_relays[i].prio);
// Set paths
s_skinPath = Engine::config().getValue("client","skinbase");
if (!s_skinPath)
s_skinPath << Engine::sharedPath() << Engine::pathSeparator() << "skins";
s_skinPath << Engine::pathSeparator();
String skin(Engine::config().getValue("client","skin","default"));
if (skin)
s_skinPath << skin;
if (!s_skinPath.endsWith(Engine::pathSeparator()))
s_skinPath << Engine::pathSeparator();
s_soundPath << Engine::sharedPath() << Engine::pathSeparator() << "sounds" <<
Engine::pathSeparator();
}
// destructor
Client::~Client()
{
// Halt the engine
Engine::halt(0);
}
bool Client::startup()
{
DDebug(ClientDriver::self(),DebugAll,"Client::startup() [%p]",this);
if (m_clientThread) {
Debug(ClientDriver::self(),DebugNote,"Trying to build a client thread when you already have one '%s' [%p]",
m_clientThread->name(),m_clientThread);
return true;
}
else {
m_clientThread = new ClientThread(this);
if (!m_clientThread->startup()) {
Debug(ClientDriver::self(),DebugWarn,"Failed to startup the client thread '%s' [%p]",m_clientThread->name(),m_clientThread);
delete static_cast<ClientThread*>(m_clientThread);
m_clientThread = 0;
return false;
}
Debug(ClientDriver::self(),DebugInfo,"Starting up client thread '%s' [%p]",m_clientThread->name(),m_clientThread);
return true;
}
return false;
}
// Cleanup before halting
void Client::cleanup()
{
s_exiting = true;
// Drop all calls
ClientDriver::dropCalls();
// Notify termination to logics
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
Debug(ClientDriver::self(),DebugAll,"Logic(%s) exitingClient() [%p]",
logic->toString().c_str(),logic);
logic->exitingClient();
}
// Make sure we drop all channels whose peers are not client channels
Message m("call.drop");
m.addParam("reason","shutdown");
Engine::dispatch(m);
TelEngine::destruct(m_defaultLogic);
exitClient();
for (ObjList* o = m_relays.skipNull(); o; o = o->skipNext())
Engine::uninstall(static_cast<MessageRelay*>(o->get()));
m_relays.clear();
ClientSound::s_soundsMutex.lock();
ClientSound::s_sounds.clear();
ClientSound::s_soundsMutex.unlock();
m_windows.clear();
s_client = 0;
m_oneThread = false;
do
idleActions();
while (ClientDriver::self() && !ClientDriver::self()->check(100000));
}
// Load windows and optionally (re)initialize the client's options
void Client::loadUI(const char* file, bool init)
{
Debug(ClientDriver::self(),DebugAll,"Client::loadUI() [%p]",this);
loadWindows(file);
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
Debug(ClientDriver::self(),DebugAll,"Logic(%s) loadedWindows() [%p]",
logic->toString().c_str(),logic);
logic->loadedWindows();
}
initWindows();
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
Debug(ClientDriver::self(),DebugAll,"Logic(%s) initializedWindows() [%p]",
logic->toString().c_str(),logic);
logic->initializedWindows();
}
if (init) {
m_initialized = false;
initClient();
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
Debug(ClientDriver::self(),DebugAll,"Logic(%s) initializedClient() [%p]",
logic->toString().c_str(),logic);
if (logic->initializedClient())
break;
}
String greeting = Engine::config().getValue("client","greeting","Yate ${version} - ${release}");
Engine::runParams().replaceParams(greeting);
if (greeting)
setStatus(greeting);
m_initialized = true;
}
// Sanity check: at least one window should be visible
ObjList* o = m_windows.skipNull();
for (; o && !getVisible(o->get()->toString()); o = o->skipNext())
;
if ((Engine::mode() == Engine::Client) && !o)
Debug(ClientDriver::self(),DebugWarn,"There is no window visible !!!");
}
// run function for the main thread
void Client::run()
{
Debug(ClientDriver::self(),DebugAll,"Client::run() [%p]",this);
ClientLogic::initStaticData();
m_defaultLogic = createDefaultLogic();
loadUI();
// Update icons
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext())
Client::updateTrayIcon(o->get()->toString());
// Run
main();
}
// Check if a message is sent by the client
bool Client::isClientMsg(Message& msg)
{
String* module = msg.getParam(YSTRING("module"));
return module && ClientDriver::self() &&
ClientDriver::self()->name() == *module;
}
// retrieve the window named by the value of "name" from the client's list of windows
Window* Client::getWindow(const String& name)
{
if (!valid())
return 0;
ObjList* l = s_client->m_windows.find(name);
return static_cast<Window*>(l ? l->get() : 0);
}
// function for obtaining a list of all windows that the client uses
ObjList* Client::listWindows()
{
if (!valid())
return 0;
ObjList* lst = 0;
for (ObjList* l = &s_client->m_windows; l; l = l->next()) {
Window* w = static_cast<Window*>(l->get());
if (w) {
if (!lst)
lst = new ObjList;
lst->append(new String(w->id()));
}
}
return lst;
}
// Open an URL (link) in the client's thread
bool Client::openUrlSafe(const String& url)
{
if (!valid())
return false;
if (s_client->needProxy()) {
// The 'false' parameter is here because there is no constructor with name only
ClientThreadProxy proxy(ClientThreadProxy::openUrl,url,false);
return proxy.execute();
}
return openUrl(url);
}
// function for setting the visibility attribute of the "name" window
bool Client::setVisible(const String& name, bool show, bool activate)
{
if (!valid())
return false;
if (s_client->needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setVisible,name,show,activate);
return proxy.execute();
}
Window* w = getWindow(name);
if (!w)
return false;
w->visible(show);
if (show && activate)
w->setActive(w->id(),true);
return true;
}
// function for obtaining the visibility status of the "name" window
bool Client::getVisible(const String& name)
{
Window* w = getWindow(name);
return w && w->visible();
}
// Retrieve the active state of a window
bool Client::getActive(const String& name)
{
Window* w = Client::self() ? Client::self()->getWindow(name) : 0;
return w && w->active();
}
// Create the default logic
ClientLogic* Client::createDefaultLogic()
{
return new DefaultLogic;
}
// function for initiating the windows
void Client::initWindows()
{
ObjList* l = &m_windows;
for (; l; l = l->next()) {
Window* w = static_cast<Window*>(l->get());
if (w)
w->init();
}
}
// function for inaitializing the client
void Client::initClient()
{
s_eventLen = Engine::config().getIntValue("client","eventlen",10240);
if (s_eventLen > 65535)
s_eventLen = 65535;
else if (s_eventLen && (s_eventLen < 1024))
s_eventLen = 1024;
// Load the settings file
s_settings = Engine::configFile("client_settings",true);
s_settings.load();
// Load the accounts file and notify logics
s_accounts = Engine::configFile("client_accounts",true);
s_accounts.load();
unsigned int n = s_accounts.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_accounts.getSection(i);
if (!sect)
continue;
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (logic->updateAccount(*sect,sect->getBoolValue("enabled",true),false))
break;
}
}
// Load the contacts file and notify logics
s_contacts = Engine::configFile("client_contacts",true);
s_contacts.load();
n = s_contacts.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_contacts.getSection(i);
if (!sect)
continue;
// Make sure we have a name
if (!sect->getParam("name"))
sect->addParam("name",*sect);
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext())
if ((static_cast<ClientLogic*>(o->get()))->updateContact(*sect,false,true))
break;
}
// Load the providers file and notify logics
s_providers = Engine::configFile("providers");
s_providers.load();
n = s_providers.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_providers.getSection(i);
if (!sect)
continue;
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext())
if ((static_cast<ClientLogic*>(o->get()))->updateProviders(*sect,false,true))
break;
}
// Load the log file and notify logics
s_history = Engine::configFile("client_history",true);
s_history.load();
n = s_history.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_history.getSection(i);
if (!sect)
continue;
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext())
if ((static_cast<ClientLogic*>(o->get()))->callLogUpdate(*sect,false,true))
break;
}
// Load the callto history
s_calltoHistory = Engine::configFile("client_calltohistory",true);
s_calltoHistory.load();
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext())
if ((static_cast<ClientLogic*>(o->get()))->calltoLoaded())
break;
}
// function for moving simultaneously two related windows
void Client::moveRelated(const Window* wnd, int dx, int dy)
{
if (!wnd)
return;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
Window* w = static_cast<Window*>(l->get());
if (w && (w != wnd) && wnd->related(w))
w->moveRel(dx,dy);
}
}
// function for opening the pop-up window that has the id "name" with the given parameters
bool Client::openPopup(const String& name, const NamedList* params, const Window* parent)
{
if (!valid())
return false;
if (s_client->needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::openPopup,name,params,parent);
return proxy.execute();
}
Window* wnd = getWindow(name);
if (!wnd)
return false;
wnd->context("");
if (params)
wnd->setParams(*params);
if (parent)
wnd->setOver(parent);
wnd->show();
return true;
}
// function for opening a message type pop-up window with the given text, parent, context
bool Client::openMessage(const char* text, const Window* parent, const char* context)
{
NamedList params("");
params.addParam("text",text);
params.addParam("modal",String::boolText(parent != 0));
params.addParam("context",context,false);
return openPopup("message",&params,parent);
}
// function for opening a confirm type pop-up window with the given text, parent, context
bool Client::openConfirm(const char* text, const Window* parent, const char* context)
{
NamedList params("");
params.addParam("text",text);
params.addParam("modal",String::boolText(parent != 0));
params.addParam("context",context,false);
return openPopup("confirm",&params,parent);
}
// check if the window has a widget named "name"
bool Client::hasElement(const String& name, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::hasElement,name,false,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->hasElement(name);
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip) && wnd->hasElement(name))
return true;
}
return false;
}
// function for controlling the visibility attribute of the "name" widget from the window given as a parameter
// if no window is given, we search for it
bool Client::setShow(const String& name, bool visible, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setShow,name,visible,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setShow(name,visible);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setShow(name,visible) || ok;
}
--s_changing;
return ok;
}
// function for controlling the enabled attribute of the "name" widget from the "wnd" window
bool Client::setActive(const String& name, bool active, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setActive,name,active,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setActive(name,active);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setActive(name,active) || ok;
}
--s_changing;
return ok;
}
// function for controlling the focus attribute of the "name" widget from the "wnd" window
bool Client::setFocus(const String& name, bool select, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setFocus,name,select,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setFocus(name,select);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setFocus(name,select) || ok;
}
--s_changing;
return ok;
}
// function for setting the text of the widget identified by "name"
bool Client::setText(const String& name, const String& text, bool richText,
Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setText,name,text,"",richText,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setText(name,text,richText);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->setText(name,text,richText) || ok;
}
--s_changing;
return ok;
}
// function that controls the checked attribute of checkable widgets
bool Client::setCheck(const String& name, bool checked, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setCheck,name,checked,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setCheck(name,checked);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setCheck(name,checked) || ok;
}
--s_changing;
return ok;
}
// function for selecting the widget named "name" from the "wnd" window if given, else look for the widget
bool Client::setSelect(const String& name, const String& item, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setSelect,name,item,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setSelect(name,item);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setSelect(name,item) || ok;
}
--s_changing;
return ok;
}
// function for handling an action that requires immediate action on the "name" widget
bool Client::setUrgent(const String& name, bool urgent, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setUrgent,name,urgent,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setUrgent(name,urgent);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setUrgent(name,urgent) || ok;
}
--s_changing;
return ok;
}
// function for checking if the "name" widget has the specified item
bool Client::hasOption(const String& name, const String& item, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::hasOption,name,item,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->hasOption(name,item);
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip) && wnd->hasOption(name,item))
return true;
}
return false;
}
// function for adding a new option to the "name" widget from the "wnd" window, if given
bool Client::addOption(const String& name, const String& item, bool atStart, const String& text, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::addOption,name,text,item,atStart,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->addOption(name,item,atStart,text);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->addOption(name,item,atStart,text) || ok;
}
--s_changing;
return ok;
}
// function for deleting an option from the "name" widget from the "wnd" window, if given
bool Client::delOption(const String& name, const String& item, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::delOption,name,item,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->delOption(name,item);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->delOption(name,item) || ok;
}
--s_changing;
return ok;
}
// Get an element's items
bool Client::getOptions(const String& name, NamedList* items,
Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getOptions,name,items,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getOptions(name,items);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->getOptions(name,items) || ok;
}
--s_changing;
return ok;
}
// Append or insert text lines to a widget
bool Client::addLines(const String& name, const NamedList* lines, unsigned int max,
bool atStart, Window* wnd, Window* skip)
{
if (!(lines && valid()))
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::addLines,name,lines,max,atStart,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->addLines(name,lines,max,atStart);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->addLines(name,lines,max,atStart) || ok;
}
--s_changing;
return ok;
}
// function for adding a new row to a table with the "name" id
bool Client::addTableRow(const String& name, const String& item, const NamedList* data,
bool atStart, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::addTableRow,name,item,atStart,data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->addTableRow(name,item,data,atStart);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->addTableRow(name,item,data,atStart) || ok;
}
--s_changing;
return ok;
}
bool Client::setMultipleRows(const String& name, const NamedList& data, const String& prefix, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setMultipleRows,name,prefix,false,&data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setMultipleRows(name,data,prefix);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->setMultipleRows(name,data,prefix) || ok;
}
--s_changing;
return ok;
}
// Function to insert a new row into a table with the "name" id
bool Client::insertTableRow(const String& name, const String& item,
const String& before, const NamedList* data, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::insertTableRow,name,before,item,data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->insertTableRow(name,item,before,data);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->insertTableRow(name,item,before,data) || ok;
}
--s_changing;
return ok;
}
// function for deleting a row from the "name" table
bool Client::delTableRow(const String& name, const String& item, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::delTableRow,name,item,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->delTableRow(name,item);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->delTableRow(name,item) || ok;
}
--s_changing;
return ok;
}
// function for changing the value of a row from the "name" table
bool Client::setTableRow(const String& name, const String& item, const NamedList* data, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setTableRow,name,item,false,data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setTableRow(name,item,data);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->setTableRow(name,item,data) || ok;
}
--s_changing;
return ok;
}
// function for obtaining the information from a specific row from the "name" table
bool Client::getTableRow(const String& name, const String& item, NamedList* data, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getTableRow,name,item,false,data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getTableRow(name,item,data);
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip) && wnd->getTableRow(name,item,data))
return true;
}
return false;
}
// Set a table row or add a new one if not found
bool Client::updateTableRow(const String& name, const String& item,
const NamedList* data, bool atStart, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::updateTableRow,name,item,
atStart,data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->updateTableRow(name,item,data,atStart);
++s_changing;
bool ok = false;
for (ObjList* l = m_windows.skipNull(); l; l = l->skipNext()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->updateTableRow(name,item,data,atStart) || ok;
}
--s_changing;
return ok;
}
// Add or set one or more table row(s)
bool Client::updateTableRows(const String& name, const NamedList* data, bool atStart,
Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::updateTableRows,name,String::empty(),
atStart,data,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->updateTableRows(name,data,atStart);
++s_changing;
bool ok = false;
for (ObjList* l = m_windows.skipNull(); l; l = l->skipNext()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->updateTableRows(name,data,atStart) || ok;
}
--s_changing;
return ok;
}
// function for deleting all row from a table given by the name parameter
bool Client::clearTable(const String& name, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::clearTable,name,false,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->clearTable(name);
++s_changing;
bool ok = false;
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip))
ok = wnd->clearTable(name) || ok;
}
--s_changing;
return ok;
}
// Show or hide control busy state
bool Client::setBusy(const String& name, bool on, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setBusy,name,on,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setBusy(name,on);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->setBusy(name,on) || ok;
}
--s_changing;
return ok;
}
// function for obtaining the text from the "name" widget
bool Client::getText(const String& name, String& text, bool richText, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getText,name,&text,&richText,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getText(name,text,richText);
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip) && wnd->getText(name,text,richText))
return true;
}
return false;
}
// function for obtaining the status of the checked attribute for the "name" checkable attribute
bool Client::getCheck(const String& name, bool& checked, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getCheck,name,0,&checked,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getCheck(name,checked);
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip) && wnd->getCheck(name,checked))
return true;
}
return false;
}
// get the iten currently selected from the "name" widget
bool Client::getSelect(const String& name, String& item, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getSelect,name,&item,0,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getSelect(name,item);
ObjList* l = &m_windows;
for (; l; l = l->next()) {
wnd = static_cast<Window*>(l->get());
if (wnd && (wnd != skip) && wnd->getSelect(name,item))
return true;
}
return false;
}
// Retrieve an element's multiple selection
bool Client::getSelect(const String& name, NamedList& items, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getSelect,name,&items,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getSelect(name,items);
for (ObjList* l = m_windows.skipNull(); l; l = l->skipNext()) {
wnd = static_cast<Window*>(l->get());
if ((wnd != skip) && wnd->getSelect(name,items))
return true;
}
return false;
}
// Build a menu from a list of parameters.
bool Client::buildMenu(const NamedList& params, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::buildMenu,String::empty(),&params,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->buildMenu(params);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->buildMenu(params) || ok;
}
--s_changing;
return ok;
}
// Remove a menu
bool Client::removeMenu(const NamedList& params, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::removeMenu,String::empty(),&params,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->removeMenu(params);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->removeMenu(params) || ok;
}
--s_changing;
return ok;
}
// Set an element's image
bool Client::setImage(const String& name, const String& image, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setImage,name,image,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setImage(name,image,false);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->setImage(name,image,false) || ok;
}
--s_changing;
return ok;
}
// Set an element's image. Request to fit the image
bool Client::setImageFit(const String& name, const String& image, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setImageFit,name,image,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setImage(name,image,true);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->setImage(name,image,true) || ok;
}
--s_changing;
return ok;
}
// Set a property
bool Client::setProperty(const String& name, const String& item, const String& value,
Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setProperty,name,value,item,false,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setProperty(name,item,value);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->setProperty(name,item,value) || ok;
}
--s_changing;
return ok;
}
// Get a property
bool Client::getProperty(const String& name, const String& item, String& value,
Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::getProperty,name,value,item,false,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->getProperty(name,item,value);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o && !ok; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->getProperty(name,item,value);
}
--s_changing;
return ok;
}
// Create a window with a given name
bool Client::createWindowSafe(const String& name, const String& alias)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::createWindow,name,alias,0,0);
return proxy.execute();
}
if (!createWindow(name,alias))
return false;
ObjList* obj = m_windows.find(alias.null() ? name : alias);
if (!obj)
return false;
(static_cast<Window*>(obj->get()))->init();
return true;
}
// Create a modal dialog owned by a given window
bool Client::createDialog(const String& name, Window* parent, const String& title,
const String& alias, const NamedList* params)
{
if (!(valid() && name && parent))
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::createDialog,name,title,alias,params,parent,0);
return proxy.execute();
}
return parent->createDialog(name,title,alias,params);
}
// Ask to an UI factory to create an object in the UI's thread
bool Client::createObject(void** dest, const String& type, const char* name,
NamedList* params)
{
if (!(dest && valid()))
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::createObject,dest,type,name,params);
return proxy.execute();
}
*dest = UIFactory::build(type,name,params);
return (0 != *dest);
}
// Hide/close a window with a given name
bool Client::closeWindow(const String& name, bool hide)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::closeWindow,name,hide);
return proxy.execute();
}
Window* wnd = getWindow(name);
if (!wnd)
return false;
if (hide)
wnd->hide();
else if (wnd->canClose())
TelEngine::destruct(wnd);
else
return false;
return true;
}
// Destroy a modal dialog
bool Client::closeDialog(const String& name, Window* wnd, Window* skip)
{
if (!valid())
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::closeDialog,name,(String*)0,0,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->closeDialog(name);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd != skip)
ok = wnd->closeDialog(name) || ok;
}
--s_changing;
return ok;
}
// Set multiple window parameters
bool Client::setParams(const NamedList* params, Window* wnd, Window* skip)
{
if (!(params && valid()))
return false;
if (needProxy()) {
ClientThreadProxy proxy(ClientThreadProxy::setParams,String::empty(),
(NamedList*)params,wnd,skip);
return proxy.execute();
}
if (wnd)
return wnd->setParams(*params);
++s_changing;
bool ok = false;
for (ObjList* o = m_windows.skipNull(); o; o = o->skipNext()) {
wnd = static_cast<Window*>(o->get());
if (wnd && (wnd != skip))
ok = wnd->setParams(*params) || ok;
}
--s_changing;
return ok;
}
bool Client::addToLog(const String& text)
{
dbg_client_func(text,-1);
return true;
}
// set the status of the client
bool Client::setStatus(const String& text, Window* wnd)
{
Debug(ClientDriver::self(),DebugInfo,"Status '%s' in window %p",text.c_str(),wnd);
addToLog(text);
return setText(s_statusWidget,text,false,wnd);
}
bool Client::setStatusLocked(const String& text, Window* wnd)
{
lockOther();
bool ok = setStatus(text,wnd);
unlockOther();
return ok;
}
// Change debug output
bool Client::debugHook(bool active)
{
if (ClientDriver::self())
ClientDriver::self()->debugEnabled(!active);
Debugger::setOutput(active ? dbg_client_func : 0);
return true;
}
// Process received messages
bool Client::received(Message& msg, int id)
{
bool processed = false;
bool stop = false;
for (ObjList* o = s_logics.skipNull(); !stop && o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) processing %s [%p]",
logic->toString().c_str(),msg.c_str(),logic);
switch (id) {
case CallCdr:
processed = logic->handleCallCdr(msg,stop) || processed;
break;
case UiAction:
processed = logic->handleUiAction(msg,stop) || processed;
break;
case UserLogin:
processed = logic->handleUserLogin(msg,stop) || processed;
break;
case UserNotify:
processed = logic->handleUserNotify(msg,stop) || processed;
break;
case ResourceNotify:
processed = logic->handleResourceNotify(msg,stop) || processed;
break;
case ResourceSubscribe:
processed = logic->handleResourceSubscribe(msg,stop) || processed;
break;
case ClientChanUpdate:
processed = logic->handleClientChanUpdate(msg,stop) || processed;
break;
case UserRoster:
processed = logic->handleUserRoster(msg,stop) || processed;
break;
case ContactInfo:
processed = logic->handleContactInfo(msg,stop) || processed;
break;
case EngineStart:
logic->engineStart(msg);
break;
default:
processed = logic->defaultMsgHandler(msg,id,stop) || processed;
}
}
return processed;
}
// Postpone messages to be redispatched from UI thread
bool Client::postpone(const Message& msg, int id, bool copyUserData)
{
if (isUIThread())
return false;
PostponedMessage* postponed = new PostponedMessage(msg,id,copyUserData);
s_postponeMutex.lock();
s_postponed.append(postponed);
s_postponeMutex.unlock();
return true;
}
// Handle actions from user interface
bool Client::action(Window* wnd, const String& name, NamedList* params)
{
static const String sect = "action";
XDebug(ClientDriver::self(),DebugAll,"Action '%s' in window (%p,%s)",
name.c_str(),wnd,wnd ? wnd->id().c_str() : "");
String substitute = name;
String handle;
bool only = false, prefer = false, ignore = false, bailout = false;
bool ok = false;
if (hasOverride(s_actions.getSection(sect),substitute,handle,only,prefer,ignore,bailout) &&
(only || prefer)) {
ok = callLogicAction(findLogic(handle),wnd,substitute,params);
bailout = only || ok;
}
if (bailout)
return ok;
for(ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (ignore && handle == logic->toString())
continue;
if (callLogicAction(logic,wnd,substitute,params))
return true;
}
// Not processed: enqueue event
Engine::enqueue(eventMessage("action",wnd,substitute,params));
return false;
}
// Deal with toggle widget events
bool Client::toggle(Window* wnd, const String& name, bool active)
{
static const String sect = "toggle";
XDebug(ClientDriver::self(),DebugAll,
"Toggle name='%s' active='%s' in window (%p,%s)",
name.c_str(),String::boolText(active),wnd,wnd ? wnd->id().c_str() : "");
String substitute = name;
String handle;
bool only = false, prefer = false, ignore = false, bailout = false;
bool ok = false;
if (hasOverride(s_actions.getSection(sect),substitute,handle,only,prefer,ignore,bailout) &&
(only || prefer)) {
ok = callLogicToggle(findLogic(handle),wnd,substitute,active);
bailout = only || ok;
}
if (bailout)
return ok;
for(ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (ignore && handle == logic->toString())
continue;
if (callLogicToggle(logic,wnd,substitute,active))
return true;
}
// Not processed: enqueue event
Message* m = eventMessage("toggle",wnd,substitute);
m->addParam("active",String::boolText(active));
Engine::enqueue(m);
return false;
}
// Handle different select() methods
static bool handleSelect(ObjList& logics, Window* wnd, const String& name,
const String& item, const String& text, const NamedList* items)
{
static const String sect = "select";
String substitute = name;
String handle;
bool only = false, prefer = false, ignore = false, bailout = false;
bool ok = false;
if (hasOverride(Client::s_actions.getSection(sect),substitute,handle,only,prefer,ignore,bailout) &&
(only || prefer)) {
ok = callLogicSelect(Client::findLogic(handle),wnd,substitute,item,text,items);
bailout = only || ok;
}
if (bailout)
return ok;
for(ObjList* o = logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (ignore && handle == logic->toString())
continue;
if (callLogicSelect(logic,wnd,substitute,item,text,items))
return true;
}
// Not processed: enqueue event
Message* m = Client::eventMessage("select",wnd,substitute);
if (!items) {
m->addParam("item",item);
m->addParam("text",text,false);
}
else {
NamedIterator iter(*items);
String prefix = "item.";
int n = 0;
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
String pref = prefix;
pref << n++;
m->addParam(pref,ns->name());
if (*ns)
m->addParam(pref + ".text",*ns);
}
}
Engine::enqueue(m);
return false;
}
// Handle selection changes (list selection changes, focus changes ...)
bool Client::select(Window* wnd, const String& name, const String& item, const String& text)
{
XDebug(ClientDriver::self(),DebugAll,
"Select name='%s' item='%s' in window (%p,%s)",
name.c_str(),item.c_str(),wnd,wnd ? wnd->id().c_str() : "");
return handleSelect(s_logics,wnd,name,item,text,0);
}
// Handle 'select' with multiple items actions from user interface
bool Client::select(Window* wnd, const String& name, const NamedList& items)
{
XDebug(ClientDriver::self(),DebugAll,
"Select name='%s' items=%p in window (%p,%s)",
name.c_str(),&items,wnd,wnd ? wnd->id().c_str() : "");
return handleSelect(s_logics,wnd,name,String::empty(),String::empty(),&items);
}
// function for setting the current line
void Client::line(int newLine)
{
Debug(ClientDriver::self(),DebugInfo,"line(%d)",newLine);
m_line = newLine;
}
// actions taken when the client is idle, has nothing to do
void Client::idleActions()
{
NamedList* log = 0;
if (s_debugLog && s_debugMutex.lock(20000)) {
log = s_debugLog;
s_debugLog = 0;
s_debugMutex.unlock();
}
// Add to the debug log new information
if (log) {
addLines(s_debugWidget,log,s_eventLen);
TelEngine::destruct(log);
}
// Tick the logics
if (s_idleLogicsTick) {
s_idleLogicsTick = false;
Time time;
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext())
(static_cast<ClientLogic*>(o->get()))->idleTimerTick(time);
}
// Dispatch postponed messages
ObjList postponed;
int postponedCount = 0;
s_postponeMutex.lock();
for (;;) {
// First move some messages from the global list to the local one
GenObject* msg = s_postponed.remove(false);
if (!msg)
break;
postponed.append(msg);
// arbitrary limit to avoid freezing the user interface
if (++postponedCount >= 16)
break;
}
s_postponeMutex.unlock();
if (postponedCount) {
Debug(ClientDriver::self(),DebugInfo,"Dispatching %d postponed messages",postponedCount);
for (;;) {
PostponedMessage* msg = static_cast<PostponedMessage*>(postponed.remove(false));
if (!msg)
break;
received(*msg,msg->id());
delete msg;
}
}
// arbitrary limit to let other threads run too
for (int i = 0; i < 4; i++) {
if (!s_busy)
return;
ClientThreadProxy* tmp = s_proxy;
s_proxy = 0;
if (!tmp)
return;
tmp->process();
}
}
// Request to a logic to set a client's parameter. Save the settings file
// and/or update interface
bool Client::setClientParam(const String& param, const String& value,
bool save, bool update)
{
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (logic->setClientParam(param,value,save,update))
return true;
}
return false;
}
// Called when the user pressed the backspace key
bool Client::backspace(const String& name, Window* wnd)
{
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (logic->backspace(name,wnd))
return true;
}
return false;
}
bool Client::driverLock(long maxwait)
{
if (maxwait < 0)
maxwait = 0;
return ClientDriver::self() && ClientDriver::self()->lock(maxwait);
}
bool Client::driverLockLoop()
{
if (!(isUIThread() && ClientDriver::self()))
return false;
while (!driverLock()) {
if (Engine::exiting() || !ClientDriver::self())
return false;
idleActions();
Thread::yield();
}
return true;
}
void Client::driverUnlock()
{
if (ClientDriver::self())
ClientDriver::self()->unlock();
}
// Create and install a message relay owned by this client.
void Client::installRelay(const char* name, int id, int prio)
{
if (!(name && *name))
return;
Debug(ClientDriver::self(),DebugAll,"installRelay(%s,%d,%d)",name,id,prio);
MessageRelay* relay = new MessageRelay(name,this,id,prio,ClientDriver::self()->name(),true);
if (Engine::install(relay))
m_relays.append(relay);
else
TelEngine::destruct(relay);
}
// Process an IM message
bool Client::imExecute(Message& msg)
{
static const String sect = "miscellaneous";
if (Client::isClientMsg(msg))
return false;
XDebug(ClientDriver::self(),DebugAll,"Client::imExecute [%p]",this);
// Check for a preferred or only logic
String name = "imincoming";
String handle;
bool only = false, prefer = false, ignore = false, bailout = false;
bool ok = false;
if (hasOverride(s_actions.getSection(sect),name,handle,only,prefer,ignore,bailout) &&
(only || prefer)) {
ClientLogic* logic = findLogic(handle);
if (logic)
ok = logic->imIncoming(msg);
bailout = only || ok;
}
if (bailout)
return ok;
// Ask the logics to create a channel
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (ignore && handle == logic->toString())
continue;
Debug(ClientDriver::self(),DebugAll,"Logic(%s) imIncoming [%p]",
logic->toString().c_str(),logic);
if (logic->imIncoming(msg))
return true;
}
return false;
}
// Build an incoming channel
bool Client::buildIncomingChannel(Message& msg, const String& dest)
{
Debug(ClientDriver::self(),DebugAll,"Client::buildIncomingChannel() [%p]",this);
if (!(msg.userData() && ClientDriver::self()))
return false;
CallEndpoint* peer = static_cast<CallEndpoint*>(msg.userData());
if (!peer)
return false;
ClientDriver::self()->lock();
ClientChannel* chan = new ClientChannel(msg,peer->id());
chan->initChan();
ClientDriver::self()->unlock();
bool ok = chan->connect(peer,msg.getValue("reason"));
// Activate or answer
if (ok) {
// Open an incoming URL if configured
if (getBoolOpt(OptOpenIncomingUrl)) {
String* url = msg.getParam(s_incomingUrlParam);
if (!null(url) && Client::self() && !Client::self()->openUrlSafe(*url))
Debug(ClientDriver::self(),DebugMild,"Failed to open incoming url=%s",url->c_str());
}
msg.setParam("targetid",chan->id());
if (!getBoolOpt(OptAutoAnswer)) {
if (getBoolOpt(OptActivateLastInCall) && !ClientDriver::self()->activeId())
ClientDriver::self()->setActive(chan->id());
}
else
chan->callAnswer();
}
TelEngine::destruct(chan);
return ok;
}
// Build an outgoing channel
bool Client::buildOutgoingChannel(NamedList& params)
{
String tmp;
#ifdef DEBUG
params.dump(tmp," ");
#endif
Debug(ClientDriver::self(),DebugAll,"Client::buildOutgoingChannel(%s) [%p]",tmp.safe(),this);
// get the target of the call
NamedString* target = params.getParam(YSTRING("target"));
if (TelEngine::null(target))
return false;
// Create the channel. Release driver's mutex as soon as possible
if (!driverLockLoop())
return false;
int st = ClientChannel::SlaveNone;
String masterChan;
NamedString* slave = params.getParam(YSTRING("channel_slave_type"));
if (slave) {
st = ClientChannel::lookupSlaveType(*slave);
params.clearParam(slave);
NamedString* m = params.getParam(YSTRING("channel_master"));
if (st && m)
masterChan = *m;
params.clearParam(m);
}
ClientChannel* chan = new ClientChannel(*target,params,st,masterChan);
chan->initChan();
if (!(chan->ref() && chan->start(*target,params)))
TelEngine::destruct(chan);
driverUnlock();
if (!chan)
return false;
params.addParam("channelid",chan->id());
if (!st && (getBoolOpt(OptActivateLastOutCall) || !ClientDriver::self()->activeId()))
ClientDriver::self()->setActive(chan->id());
TelEngine::destruct(chan);
return true;
}
// Call execute handler called by the driver
bool Client::callIncoming(Message& msg, const String& dest)
{
static const String sect = "miscellaneous";
XDebug(ClientDriver::self(),DebugAll,"Client::callIncoming [%p]",this);
// if we are in single line mode and we have already a channel, reject the call
if (ClientDriver::self() && ClientDriver::self()->isBusy() && !getBoolOpt(OptMultiLines)) {
msg.setParam("error","busy");
msg.setParam("reason",s_userBusy);
return false;
}
// Check for a preferred or only logic
String name = "callincoming";
String handle;
bool only = false, prefer = false, ignore = false, bailout = false;
bool ok = false;
if (hasOverride(s_actions.getSection(sect),name,handle,only,prefer,ignore,bailout) &&
(only || prefer)) {
ClientLogic* logic = findLogic(handle);
if (logic)
ok = logic->callIncoming(msg,dest);
bailout = only || ok;
}
if (bailout)
return ok;
// Ask the logics to create a channel
for (ObjList* o = s_logics.skipNull(); o; o = o->skipNext()) {
ClientLogic* logic = static_cast<ClientLogic*>(o->get());
if (ignore && handle == logic->toString())
continue;
Debug(ClientDriver::self(),DebugAll,"Logic(%s) callIncoming [%p]",
logic->toString().c_str(),logic);
if (logic->callIncoming(msg,dest))
return true;
}
return false;
}
// Accept an incoming call
void Client::callAnswer(const String& id, bool setActive)
{
Debug(ClientDriver::self(),DebugInfo,"callAccept('%s')",id.c_str());
if (!driverLockLoop())
return;
ClientChannel* chan = static_cast<ClientChannel*>(ClientDriver::self()->find(id));
if (chan)
chan->callAnswer(setActive);
driverUnlock();
}
// Terminate a call
void Client::callTerminate(const String& id, const char* reason, const char* error)
{
Debug(ClientDriver::self(),DebugInfo,"callTerminate(%s)",id.c_str());
// Check if the channel exists
Lock lock(ClientDriver::self());
if (!ClientDriver::self())
return;
Channel* chan = ClientDriver::self()->find(id);
if (!chan)
return;
bool hangup = chan->isAnswered();
bool cancel = !hangup && chan->isIncoming();
lock.drop();
// Drop the call
Message* m = new Message("call.drop");
m->addParam("id",id);
if (hangup || cancel) {
if (!reason && cancel)
reason = "cancelled";
if (!error)
error = cancel ? s_cancelReason : s_hangupReason;
}
else {
if (!reason)
reason = "busy";
if (!error)
error = s_rejectReason;
}
m->addParam("error",error,false);
m->addParam("reason",reason,false);
Engine::enqueue(m);
}
// Get the active channel if any
ClientChannel* Client::getActiveChannel()
{
return ClientDriver::self() ? ClientDriver::self()->findActiveChan() : 0;
}
// Start/stop ringer
bool Client::ringer(bool in, bool on)
{
String* what = in ? &s_ringInName : &s_ringOutName;
bool ok = in ? getBoolOpt(OptRingIn) : getBoolOpt(OptRingOut);
Lock lock(ClientSound::s_soundsMutex);
DDebug(ClientDriver::self(),DebugAll,"Ringer in=%s on=%s",
String::boolText(in),String::boolText(on));
if (!on)
ClientSound::stop(*what);
else if (*what)
return ok && ClientSound::start(*what,false);
else
return false;
return true;
}
// Send DTMFs on selected channel
bool Client::emitDigits(const char* digits, const String& id)
{
XDebug(ClientDriver::self(),DebugAll,"Client::emitDigits(%s,%s)",digits,id.c_str());
if (!driverLockLoop())
return false;
Channel* chan = !id ? ClientDriver::self()->find(ClientDriver::self()->activeId()) :
ClientDriver::self()->find(id);
bool ok = (0 != chan);
if (ok) {
Debug(chan,DebugAll,"emitDigits(%s) [%p]",digits,chan);
Message* m = chan->message("chan.dtmf");
m->addParam("text",digits);
Engine::enqueue(m);
}
driverUnlock();
return ok;
}
// Set a boolean option of this client
bool Client::setBoolOpt(ClientToggle toggle, bool value, bool updateUi)
{
if (toggle >= OptCount)
return false;
if (m_toggles[toggle] == value && !updateUi)
return false;
m_toggles[toggle] = value;
if (updateUi)
setCheck(s_toggles[toggle],value);
// Special options
switch (toggle) {
case OptRingIn:
if (!value)
ringer(true,false);
break;
case OptRingOut:
if (!value)
ringer(false,false);
break;
default: ;
}
return true;
}
// Build a message to be sent by the client.
Message* Client::buildMessage(const char* msg, const String& account, const char* oper)
{
Message* m = new Message(msg);
if (ClientDriver::self())
m->addParam("module",ClientDriver::self()->name());
m->addParam("operation",oper,false);
m->addParam("account",account);
return m;
}
// Build a resource.notify message
Message* Client::buildNotify(bool online, const String& account, const ClientResource* from)
{
Message* m = buildMessage("resource.notify",account,online ? "online" : "offline");
if (from) {
m->addParam("priority",String(from->m_priority));
m->addParam("status",from->m_text);
if (from->m_status > ClientResource::Online)
m->addParam("show",lookup(from->m_status,ClientResource::s_statusName));
}
return m;
}
// Build a resource.subscribe or resource.notify message to request a subscription
// or respond to a request
Message* Client::buildSubscribe(bool request, bool ok, const String& account,
const String& contact, const char* proto)
{
Message* m = 0;
if (request)
m = buildMessage("resource.subscribe",account,ok ? "subscribe" : "unsubscribe");
else
m = buildMessage("resource.notify",account,ok ? "subscribed" : "unsubscribed");
m->addParam("protocol",proto,false);
m->addParam("to",contact);
return m;
}
// Build an user.roster message
Message* Client::buildUserRoster(bool update, const String& account,
const String& contact, const char* proto)
{
Message* m = buildMessage("user.roster",account,update ? "update" : "delete");
m->addParam("protocol",proto,false);
m->addParam("contact",contact);
return m;
}
// Add a new module for handling actions
bool Client::addLogic(ClientLogic* logic)
{
static const NamedList* s_load = 0;
// Load logic actions file
if (!s_actions.getSection(0)) {
s_actions = Engine::configFile("client_actions",false);
s_actions.load();
s_load = s_actions.getSection("load");
}
if (!logic || s_logics.find(logic))
return false;
// Check if we should accept logic load
// If not in config, accept only if priority is negative
// Else: check boolean value or accept only valid positive integer values
String* param = s_load ? s_load->getParam(logic->toString()) : 0;
bool deny = true;
if (param) {
if (param->isBoolean())
deny = !param->toBoolean();
else
deny = param->toInteger(-1) < 0;
}
else if (logic->priority() < 0)
deny = false;
if (deny) {
Debug(DebugInfo,"Skipping client logic %p name=%s prio=%d%s%s",
logic,logic->toString().c_str(),logic->priority(),
param ? " config value: " : " not found in config",
param ? param->c_str() : "");
return false;
}
// Add the logic
if (logic->priority() < 0)
logic->m_prio = -logic->priority();
bool dup = (0 != s_logics.find(logic->toString()));
Debug(dup ? DebugGoOn : DebugInfo,"Adding client logic%s %p name=%s prio=%d",
dup ? " [DUPLICATE]" : "",logic,logic->toString().c_str(),logic->priority());
for (ObjList* l = s_logics.skipNull(); l; l = l->skipNext()) {
ClientLogic* obj = static_cast<ClientLogic*>(l->get());
if (logic->priority() <= obj->priority()) {
l->insert(logic)->setDelete(false);
return true;
}
}
s_logics.append(logic)->setDelete(false);
return true;
}
// Remove a logic from the list
void Client::removeLogic(ClientLogic* logic)
{
if (!(logic && s_logics.find(logic)))
return;
Debug(ClientDriver::self(),DebugInfo,"Removing logic %p name=%s",
logic,logic->toString().c_str());
s_logics.remove(logic,false);
}
// Convenience method to retrieve a logic
inline ClientLogic* Client::findLogic(const String& name)
{
ObjList* o = s_logics.find(name);
return o ? static_cast<ClientLogic*>(o->get()) : 0;
}
// Save a configuration file. Call openMessage() on failure
bool Client::save(Configuration& cfg, Window* parent, bool showErr)
{
DDebug(ClientDriver::self(),DebugAll,"Saving '%s'",cfg.c_str());
if (cfg.save())
return true;
String s = "Failed to save configuration file " + cfg;
if (!(showErr && self() && self()->openMessage(s,parent)))
Debug(ClientDriver::self(),DebugWarn,"%s",s.c_str());
return false;
}
// Check if a string names a client's boolean option
Client::ClientToggle Client::getBoolOpt(const String& name)
{
for (int i = 0; i < OptCount; i++)
if (s_toggles[i] == name)
return (ClientToggle)i;
return OptCount;
}
// Append URI escaped String items to a String buffer
void Client::appendEscape(String& buf, ObjList& list, char sep, bool force)
{
String tmp(sep);
for (ObjList* o = list.skipNull(); o; o = o->skipNext())
buf.append(o->get()->toString().uriEscape(sep),tmp,force);
}
// Splits a string at a delimiter character. URI unescape each string in result
ObjList* Client::splitUnescape(const String& buf, char sep, bool emptyOk)
{
ObjList* list = buf.split(sep,emptyOk);
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
*s = s->uriUnescape();
}
return list;
}
// Remove characters from a given string
void Client::removeChars(String& buf, const char* chars)
{
if (TelEngine::null(chars))
return;
int pos = 0;
while (*chars) {
pos = buf.find(*chars,pos);
if (pos == -1) {
chars++;
pos = 0;
}
else
buf = buf.substr(0,pos) + buf.substr(pos + 1);
}
}
// Fix a phone number. Remove extra '+' from begining.
// Remove requested characters.
// Clear the number if a non digit char is found
void Client::fixPhoneNumber(String& number, const char* chars)
{
if (!number)
return;
unsigned int n = 0;
// Remove extra '+' from begining
while (n < number.length() && number[n] == '+')
n++;
bool plus = false;
if (n) {
plus = true;
number = number.substr(n);
}
// Remove requested chars
removeChars(number,chars);
// Check for valid number (digits only)
for (n = 0; n < number.length(); n++) {
switch (number[n]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
}
number.clear();
break;
}
if (number && plus)
number = "+" + number;
}
// Add a tray icon to a window's stack.
bool Client::addTrayIcon(const String& wndName, int prio, NamedList* params)
{
if (!params)
return false;
if (!(wndName && valid())) {
TelEngine::destruct(params);
return false;
}
NamedPointer* wnd = YOBJECT(NamedPointer,s_trayIcons.getParam(wndName));
if (!wnd) {
wnd = new NamedPointer(wndName);
s_trayIcons.addParam(wnd);
}
ObjList* list = YOBJECT(ObjList,wnd);
if (!list) {
list = new ObjList;
wnd->userData(list);
}
ObjList* trayIcon = list->find(*params);
TrayIconDef* def = 0;
if (!trayIcon) {
ObjList* o = list->skipNull();
for (; o; o = o->skipNext()) {
TrayIconDef* d = static_cast<TrayIconDef*>(o->get());
if (d->m_priority < prio)
break;
}
def = new TrayIconDef(prio,params);
if (o)
trayIcon = o->insert(def);
else
trayIcon = list->append(def);
}
else {
def = static_cast<TrayIconDef*>(trayIcon->get());
def->userData(params);
}
// Update
if (Client::self()->initialized() && (trayIcon == list->skipNull()))
return updateTrayIcon(wndName);
return true;
}
// Remove a tray icon from a window's stack.
// Show the next one if it's the first
bool Client::removeTrayIcon(const String& wndName, const String& name)
{
if (!(wndName && name && valid()))
return false;
NamedPointer* wnd = YOBJECT(NamedPointer,s_trayIcons.getParam(wndName));
if (!wnd)
return false;
ObjList* list = YOBJECT(ObjList,wnd);
if (!list)
return false;
ObjList* trayIcon = list->find(name);
if (!trayIcon)
return false;
bool upd = Client::self()->initialized() && (trayIcon == list->skipNull());
trayIcon->remove();
if (!upd)
return false;
if (list->skipNull())
return updateTrayIcon(wndName);
// Remove the old one and update the icon
Window* w = Client::self()->getWindow(wndName);
if (w) {
NamedList p("systemtrayicon");
p.addParam("stackedicon","");
Client::self()->setParams(&p,w);
}
return true;
}
// Update the first tray icon in a window's stack.
// Remove any existing icon the the stack is empty
bool Client::updateTrayIcon(const String& wndName)
{
if (!(wndName && valid()))
return false;
Window* w = Client::self()->getWindow(wndName);
if (!w)
return false;
NamedPointer* wnd = YOBJECT(NamedPointer,s_trayIcons.getParam(wndName));
if (!wnd)
return false;
ObjList* list = YOBJECT(ObjList,wnd);
if (!list)
return false;
ObjList* o = list->skipNull();
TrayIconDef* def = o ? static_cast<TrayIconDef*>(o->get()) : 0;
NamedList p("systemtrayicon");
NamedPointer* np = 0;
if (def) {
// Add or replace
NamedList* nl = YOBJECT(NamedList,def);
np = new NamedPointer("stackedicon",nl,String::boolText(true));
p.addParam(np);
}
else
// Remove the old one
p.addParam("stackedicon","");
bool ok = Client::self()->setParams(&p,w);
if (np)
np->takeData();
return ok;
}
// Generate a GUID string in the format 8*HEX-4*HEX-4*HEX-4*HEX-12*HEX
void Client::generateGuid(String& buf, const String& extra)
{
int8_t data[16];
*(int32_t*)(data + 12) = (u_int32_t)Random::random();
*(u_int64_t*)(data + 3) = Time::now();
if (extra)
*(u_int16_t*)(data + 11) = extra.hash();
int32_t* d = (int32_t*)data;
*d = (int32_t)Random::random();
String tmp;
tmp.hexify(data,16);
buf.clear();
buf << tmp.substr(0,8) << "-" << tmp.substr(8,4) << "-";
buf << tmp.substr(12,4) << "-" << tmp.substr(16,4) << "-";
buf << tmp.substr(20);
}
// Replace plain text chars with HTML escape or markup
void Client::plain2html(String& buf, bool spaceEol)
{
static const String space = " ";
static const String htmlBr = "<br>";
static const String htmlAmp = "&amp;";
static const String htmlLt = "&lt;";
static const String htmlGt = "&gt;";
static const String htmlQuot = "&quot;";
unsigned int i = 0;
while (i < buf.length()) {
const String* mark = 0;
if (buf[i] == '\r' || buf[i] == '\n')
mark = spaceEol ? &space : &htmlBr;
else if (buf[i] == '&')
mark = &htmlAmp;
else if (buf[i] == '<')
mark = &htmlLt;
else if (buf[i] == '>')
mark = &htmlGt;
else if (buf[i] == '\"')
mark = &htmlQuot;
else {
i++;
continue;
}
// Handle "\r\n" as single <br>
if (buf[i] == '\r' && i != buf.length() - 1 && buf[i + 1] == '\n')
buf = buf.substr(0,i) + *mark + buf.substr(i + 2);
else
buf = buf.substr(0,i) + *mark + buf.substr(i + 1);
i += mark->length();
}
}
// Find a list parameter by its value
NamedString* Client::findParamByValue(NamedList& list, const String& value,
NamedString* skip)
{
NamedIterator iter(list);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (skip && skip == ns)
continue;
if (*ns == value)
return (NamedString*)ns;
}
return 0;
}
static bool lookupFlag(const char* what, const TokenDict* dict, int& flags)
{
bool on = true;
if (what[0] == '!') {
what++;
on = false;
}
int n = lookup(what,dict);
if (!n)
return false;
if (on)
flags |= n;
else
flags &= ~n;
return true;
}
// Decode flags from dictionary values found in a list of parameters
int Client::decodeFlags(const TokenDict* dict, const NamedList& params, const String& prefix)
{
int flgs = 0;
if (!dict)
return 0;
NamedIterator iter(params);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (!*ns)
continue;
const char* what = ns->name();
if (prefix) {
if (!ns->name().startsWith(prefix))
continue;
what += prefix.length();
}
lookupFlag(what,dict,flgs);
}
return flgs;
}
// Decode flags from dictionary values and comma separated list
int Client::decodeFlags(const TokenDict* dict, const String& flags, int defVal)
{
if (!(dict && flags))
return defVal;
int value = 0;
bool found = false;
ObjList* list = flags.split(',',false);
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
const String& s = o->get()->toString();
found = lookupFlag(s,dict,value) || found;
}
TelEngine::destruct(list);
return found ? value : defVal;
}
// Add path separator at string end. Set destination string.
void Client::addPathSep(String& dest, const String& path, char sep)
{
if (!path)
return;
if (!sep)
sep = *Engine::pathSeparator();
dest = path;
unsigned int len = path.length();
if (path[len - 1] != sep)
dest += sep;
}
// Fix path separator. Set it to platform default
void Client::fixPathSep(String& path)
{
char repl = (*Engine::pathSeparator() == '/') ? '\\' : '/';
char* s = (char*)path.c_str();
for (unsigned int i = 0; i < path.length(); i++, s++)
if (*s == repl)
*s = *Engine::pathSeparator();
}
// Remove chars from string end. Set destination string
bool Client::removeEndsWithPathSep(String& dest, const String& src, char sep)
{
if (!sep)
sep = *Engine::pathSeparator();
int pos = (int)(src.length()) - 1;
if (pos >= 0 && src[pos] == sep)
dest = src.substr(0,pos);
else
dest = src;
return !dest.null();
}
// Set destination from last item in path
bool Client::getLastNameInPath(String& dest, const String& path, char sep)
{
int pos = path.rfind(sep ? sep : *Engine::pathSeparator());
if (pos >= 0)
dest = path.substr(pos + 1);
if (!dest)
dest = path;
return !dest.null();
}
// Remove last name in path, set destination from remaining
bool Client::removeLastNameInPath(String& dest, const String& path, char sep,
const String& equalOnly)
{
int pos = path.rfind(sep ? sep : *Engine::pathSeparator());
bool ok = pos >= 0 && (!equalOnly || equalOnly == path.substr(pos + 1));
if (ok)
dest = path.substr(0,pos);
return ok;
}
// Add a formatted log line
bool Client::addToLogFormatted(const char* format, ...)
{
char buf[OUT_BUFFER_SIZE];
va_list va;
va_start(va,format);
::vsnprintf(buf,sizeof(buf) - 1,format,va);
va_end(va);
dbg_client_func(buf,-1);
return true;
}
// Build an 'ui.event' message
Message* Client::eventMessage(const String& event, Window* wnd, const char* name,
NamedList* params)
{
Message* m = new Message("ui.event");
if (wnd)
m->addParam("window",wnd->id());
m->addParam("event",event);
m->addParam("name",name,false);
if (params)
m->copyParams(*params);
return m;
}
// Incoming (from engine) constructor
ClientChannel::ClientChannel(const Message& msg, const String& peerid)
: Channel(ClientDriver::self(),0,true),
m_slave(SlaveNone),
m_party(msg.getValue(YSTRING("caller"))), m_noticed(false),
m_line(0), m_active(false), m_silence(false), m_conference(false),
m_muted(false), m_clientData(0), m_utility(false), m_clientParams("")
{
Debug(this,DebugCall,"Created incoming from=%s peer=%s [%p]",
m_party.c_str(),peerid.c_str(),this);
const char* acc = msg.getValue(YSTRING("in_line"));
if (TelEngine::null(acc))
acc = msg.getValue(YSTRING("account"),msg.getValue(YSTRING("line")));
if (!TelEngine::null(acc)) {
m_clientParams.addParam("account",acc);
m_clientParams.addParam("line",acc);
}
const char* proto = msg.getValue(YSTRING("protocol"));
if (TelEngine::null(proto)) {
const String& module = msg[YSTRING("module")];
if (module == YSTRING("sip") || module == YSTRING("jingle") ||
module == YSTRING("iax") || module == YSTRING("h323"))
proto = module;
}
m_clientParams.addParam("protocol",proto,false);
m_partyName = msg.getValue(YSTRING("callername"));
m_targetid = peerid;
m_peerId = peerid;
Message* s = message("chan.startup");
s->copyParams(msg,YSTRING("caller,callername,called,billid,callto,username"));
String* cs = msg.getParam(YSTRING("chanstartup_parameters"));
if (!null(cs))
s->copyParams(msg,*cs);
Engine::enqueue(s);
update(Startup,true,true,"call.ringing",false,true);
}
// Outgoing (to engine) constructor
ClientChannel::ClientChannel(const String& target, const NamedList& params,
int st, const String& masterChan)
: Channel(ClientDriver::self(),0,false),
m_slave(st),
m_party(target), m_noticed(true), m_line(0), m_active(false),
m_silence(true), m_conference(false), m_muted(false), m_clientData(0),
m_utility(false), m_clientParams("")
{
Debug(this,DebugCall,"Created outgoing to=%s [%p]",
m_party.c_str(),this);
m_partyName = params.getValue(YSTRING("calledname"));
if (m_slave)
m_master = masterChan;
}
// Constructor for utility channels used to play notifications
ClientChannel::ClientChannel(const String& soundId)
: Channel(ClientDriver::self(),0,true),
m_slave(SlaveNone),
m_noticed(true), m_line(0), m_active(false), m_silence(true),
m_conference(false), m_clientData(0), m_utility(true),
m_soundId(soundId), m_clientParams("")
{
Lock lock(ClientSound::s_soundsMutex);
ClientSound* s = ClientSound::find(m_soundId);
if (s) {
s->setChannel(id(),true);
update(Startup);
}
else
m_soundId = "";
}
// Destructor
ClientChannel::~ClientChannel()
{
XDebug(this,DebugInfo,"ClientChannel::~ClientChannel() [%p]",this);
}
// Init and start router for an outgoing (to engine), not utility, channel
bool ClientChannel::start(const String& target, const NamedList& params)
{
static const String s_cpParams("line,protocol,account,caller,callername,domain,cdrwrite");
// Build the call.route and chan.startup messages
Message* m = message("call.route");
Message* s = message("chan.startup");
// Make sure we set the target's protocol if we have one
static const Regexp r("^[a-z0-9]\\+/");
String to = target;
const char* param = "callto";
if (!r.matches(target.safe())) {
const char* proto = params.getValue("protocol");
if (proto)
to = String(proto) + "/" + target;
else
param = "called";
}
m->setParam(param,to);
s->setParam("called",to);
m->copyParams(params,s_cpParams);
s->copyParams(params,s_cpParams);
String* cs = params.getParam(YSTRING("chanstartup_parameters"));
if (!null(cs))
s->copyParams(params,*cs);
String cParams = params.getParam(YSTRING("call_parameters"));
if (cParams)
m->copyParams(params,cParams);
cParams.append("call_parameters,line,protocol,account",",");
cParams.append(params.getValue(YSTRING("client_parameters")),",");
m_clientParams.copyParams(params,cParams);
Engine::enqueue(s);
if (startRouter(m)) {
update(Startup);
return true;
}
return false;
}
void ClientChannel::destroyed()
{
Debug(this,DebugCall,"Destroyed [%p]",this);
// Drop all slaves
for (ObjList* o = m_slaves.skipNull(); o; o = o->skipNext())
ClientDriver::dropChan(o->get()->toString());
if (m_utility) {
Lock lock(ClientSound::s_soundsMutex);
ClientSound* s = ClientSound::find(m_soundId);
if (s) {
update(Destroyed,false);
s->setChannel(id(),false);
}
m_soundId = "";
lock.drop();
Lock lck(m_mutex);
setClientData();
lck.drop();
Channel::destroyed();
return;
}
Lock lock(m_mutex);
if (m_conference) {
// Drop old peer if conference
if (ClientDriver::s_dropConfPeer)
ClientDriver::dropChan(m_peerId,"Conference terminated");
}
else if (m_transferId)
ClientDriver::setAudioTransfer(id());
// Reset driver's active id
ClientDriver* drv = static_cast<ClientDriver*>(driver());
if (drv && id() == drv->activeId())
drv->setActive();
setMedia();
update(Destroyed,false,false,"chan.hangup");
setClientData();
lock.drop();
Channel::destroyed();
}
void ClientChannel::connected(const char* reason)
{
DDebug(this,DebugCall,"Connected reason=%s [%p]",reason,this);
Channel::connected(reason);
if (!m_utility)
return;
// Utility channel: set media
if (ClientDriver::self() && ClientDriver::self()->activeId())
return;
String dev = ClientDriver::device();
if (dev.null())
return;
DDebug(this,DebugCall,"Utility channel opening media [%p]",this);
Message m("chan.attach");
complete(m,true);
m.userData(this);
m.clearParam(YSTRING("id"));
m.setParam("consumer",dev);
ClientSound::s_soundsMutex.lock();
ClientSound* s = ClientSound::find(m_soundId);
if (s && s->stereo())
m.addParam("stereo",String::boolText(true));
ClientSound::s_soundsMutex.unlock();
Engine::dispatch(m);
if (!getConsumer())
Debug(this,DebugNote,"Utility channel failed to set data consumer [%p]",this);
}
void ClientChannel::disconnected(bool final, const char* reason)
{
Debug(this,DebugCall,"Disconnected reason=%s [%p]",reason,this);
Channel::disconnected(final,reason);
if (!m_reason)
m_reason = reason;
setActive(false);
// Reset transfer
if (m_transferId && !m_conference)
ClientDriver::setAudioTransfer(id());
}
// Check if our consumer's source sent any data
// Don't set the silence flag is already reset
void ClientChannel::checkSilence()
{
if (!m_silence)
return;
m_silence = !(getConsumer() && getConsumer()->getConnSource() &&
DataNode::invalidStamp() != getConsumer()->getConnSource()->timeStamp());
#ifdef DEBUG
if (!m_silence)
Debug(this,DebugInfo,"Got audio data [%p]",this);
#endif
}
// Open/close media
bool ClientChannel::setMedia(bool open, bool replace)
{
Lock lock(m_mutex);
// Check silence (we might already have a consumer)
checkSilence();
// Remove source/consumer if replacing
if (!open) {
if (getSource() || getConsumer()) {
Debug(this,DebugInfo,"Closing media channels [%p]",this);
setSource();
setConsumer();
}
return true;
}
String dev = ClientDriver::device();
if (dev.null())
return false;
if (!replace && getSource() && getConsumer())
return true;
Debug(this,DebugAll,"Opening media channels [%p]",this);
Message m("chan.attach");
complete(m,true);
m.userData(this);
m.setParam("consumer",dev);
if (!m_muted)
m.setParam("source",dev);
Engine::dispatch(m);
if (getConsumer())
checkSilence();
else
Debug(this,DebugNote,"Failed to set data consumer [%p]",this);
if (!(getSource() || m_muted))
Debug(this,DebugNote,"Failed to set data source [%p]",this);
bool ok = ((m_muted || getSource()) && getConsumer());
update(AudioSet);
lock.drop();
if (!ok && Client::self())
Client::self()->addToLog("Failed to open media channel(s): " + id());
return ok;
}
// Set/reset this channel's data source/consumer
bool ClientChannel::setActive(bool active, bool upd)
{
if (m_utility)
return false;
Lock lock(m_mutex);
// Don't activate it if envolved in a transfer
noticed();
if (active && m_transferId && !m_conference)
return false;
// Reset data source to make sure we remove any MOH
if (active)
setSource();
if (isAnswered())
setMedia(active);
// Don't notify if nothing changed
if (m_active == active)
return true;
Debug(this,DebugInfo,"Set active=%s [%p]",String::boolText(active),this);
m_active = active;
if (!upd)
return true;
update(active ? Active : OnHold);
// TODO: notify the peer if answered
return true;
}
// Set/reset this channel's muted flag. Set media if on
bool ClientChannel::setMuted(bool on, bool upd)
{
Lock lock(m_mutex);
if (m_muted == on)
return true;
Debug(this,DebugInfo,"Set muted=%s [%p]",String::boolText(on),this);
m_muted = on;
if (m_active) {
if (m_muted)
setSource();
else
setMedia(true);
}
if (upd)
update(Mute);
return true;
}
// Set/reset the transferred peer's id. Enqueue clientchan.update if changed
void ClientChannel::setTransfer(const String& target)
{
Lock lock(m_mutex);
if (m_conference || m_transferId == target)
return;
if (target)
Debug(this,DebugCall,"Transferred to '%s' [%p]",target.c_str(),this);
else
Debug(this,DebugCall,"Transfer released [%p]",this);
m_transferId = target;
setMedia(!m_transferId && m_active && isAnswered());
update(Transfer);
}
// Set/reset the conference data. Enqueue clientchan.update if changed.
void ClientChannel::setConference(const String& target)
{
Lock lock(m_mutex);
if (m_transferId == target && !m_transferId)
return;
Debug(this,DebugCall,"%sing conference room '%s' [%p]",
target ? "Enter" : "Exit",target ? target.c_str() : m_transferId.c_str(),this);
m_transferId = target;
m_conference = (0 != m_transferId);
setMedia(m_active && isAnswered());
update(Conference);
}
// Notice this channel. Enqueue a clientchan.update message
void ClientChannel::noticed()
{
Lock lock(m_mutex);
if (m_noticed)
return;
m_noticed = true;
update(Noticed);
}
// Set the channel's line (address)
void ClientChannel::line(int newLine)
{
Lock lock(m_mutex);
m_line = newLine;
m_address.clear();
if (m_line > 0) {
m_address << "line/" << m_line;
update(AddrChanged);
}
}
// Outgoing call routed: enqueue update message
bool ClientChannel::callRouted(Message& msg)
{
Lock lock(m_mutex);
update(Routed,true,false);
return true;
}
// Outgoing call accepted: enqueue update message
void ClientChannel::callAccept(Message& msg)
{
Debug(this,DebugCall,"callAccept() [%p]",this);
Channel::callAccept(msg);
Lock lock(m_mutex);
getPeerId(m_peerId);
Debug(this,DebugInfo,"Peer id set to %s",m_peerId.c_str());
// Join to master conference
if (m_slave == SlaveConference && m_master) {
String confName("conf/" + m_master);
Message m("call.conference");
m.addParam("room",confName);
m.addParam("notify",confName);
m.addParam("maxusers",String(Client::s_maxConfPeers * 2));
m.userData(this);
if (Engine::dispatch(m))
setConference(confName);
}
update(Accepted);
}
// Outgoing call rejected: reset and and enqueue update message
void ClientChannel::callRejected(const char* error, const char* reason, const Message* msg)
{
Debug(this,DebugCall,"callRejected('%s','%s',%p) [%p]",
error,reason,msg,this);
setMedia();
if (!reason)
reason = error;
if (!reason)
reason = "Unknown reason";
Channel::callRejected(error,reason,msg);
setActive(false);
m_reason = reason;
update(Rejected,true,false);
}
// Outgoing call progress
// Check for early media: start ringing tone if missing and the channel is active
// Enqueue update message
bool ClientChannel::msgProgress(Message& msg)
{
Debug(this,DebugCall,"msgProgress() [%p]",this);
if (active() && peerHasSource(msg))
setMedia(true);
bool ret = Channel::msgProgress(msg);
update(Progressing);
if (m_slave == SlaveTransfer && m_master && !m_transferId)
ClientDriver::setAudioTransfer(m_master,id());
return ret;
}
// Outgoing call ringing
// Check for early media: start ringing tone if missing and the channel is active
// Enqueue update message
bool ClientChannel::msgRinging(Message& msg)
{
Debug(this,DebugCall,"msgRinging() [%p]",this);
if (active() && peerHasSource(msg))
setMedia(true);
bool ret = Channel::msgRinging(msg);
update(Ringing);
if (m_slave == SlaveTransfer && m_master && !m_transferId)
ClientDriver::setAudioTransfer(m_master,id());
return ret;
}
// set status for when a call was answered, set the flags for different actions
// accordingly, and attach media channels
bool ClientChannel::msgAnswered(Message& msg)
{
Lock lock(m_mutex);
Debug(this,DebugCall,"msgAnswered() [%p]",this);
m_reason.clear();
if (m_slave == SlaveTransfer && m_master && !m_transferId)
ClientDriver::setAudioTransfer(m_master,id());
// Active: Open media if the peer has a source
if (active() && peerHasSource(msg))
setMedia(true);
m_silence = false;
bool ret = Channel::msgAnswered(msg);
update(Answered);
return ret;
}
// Dropped notification
bool ClientChannel::msgDrop(Message& msg, const char* reason)
{
Lock lock(m_mutex);
noticed();
Debug(this,DebugCall,"msgDrop() reason=%s [%p]",reason,this);
if (!m_reason)
m_reason = reason;
// Reset transfer
if (m_transferId && !m_conference)
ClientDriver::setAudioTransfer(id());
setActive(false,!Engine::exiting());
lock.drop();
return Channel::msgDrop(msg,reason);
}
// Answer the call if not answered
// Activate the channel
void ClientChannel::callAnswer(bool setActive)
{
Lock lock(m_mutex);
noticed();
if (!isAnswered()) {
Debug(this,DebugCall,"callAnswer() [%p]",this);
m_reason.clear();
status("answered");
update(Answered,true,true,"call.answered",false,true);
}
// Activating channel will set the media
if (setActive && ClientDriver::self())
ClientDriver::self()->setActive(id());
}
// Enqueue clientchan.update message
void ClientChannel::update(int notif, bool chan, bool updatePeer,
const char* engineMsg, bool minimal, bool data)
{
if (m_utility) {
if (m_soundId) {
const char* op = lookup(notif);
if (!op)
return;
Message* m = new Message("clientchan.update");
m->addParam("notify",op);
m->addParam("utility",String::boolText(true));
m->addParam("sound",m_soundId);
Engine::enqueue(m);
}
return;
}
if (engineMsg)
Engine::enqueue(message(engineMsg,minimal,data));
if (updatePeer) {
CallEndpoint* peer = getPeer();
if (peer && peer->ref()) {
if (peer->getConsumer())
m_peerOutFormat = peer->getConsumer()->getFormat();
if (peer->getSource())
m_peerInFormat = peer->getSource()->getFormat();
TelEngine::destruct(peer);
}
}
const char* op = lookup(notif);
if (!op)
return;
Message* m = new Message("clientchan.update");
m->addParam("notify",op);
// Add extended params only if we don't set the channel
if (chan)
m->userData(this);
else {
m->userData(m_clientData);
m->addParam("id",id());
m->addParam("direction",isOutgoing() ? "incoming" : "outgoing");
m->addParam("address",m_address,false);
if (notif != Noticed && m_noticed)
m->addParam("noticed",String::boolText(true));
if (m_active)
m->addParam("active",String::boolText(true));
m->addParam("transferid",m_transferId,false);
if (m_conference)
m->addParam("conference",String::boolText(m_conference));
if (m_slave) {
m->addParam("channel_slave_type",::lookup(m_slave,s_slaveTypes),false);
m->addParam("channel_master",m_master);
}
}
if (m_silence)
m->addParam("silence",String::boolText(true));
Engine::enqueue(m);
}
// Get channel peer used to reconnect
CallEndpoint* ClientChannel::getReconnPeer(bool ref)
{
String tmp;
getReconnPeer(tmp);
if (!tmp)
return 0;
Message m("chan.locate");
m.addParam("id",tmp);
Engine::dispatch(m);
CallEndpoint* c = static_cast<Channel*>(YOBJECT(CallEndpoint,m.userData()));
return (c && (!ref || c->ref())) ? c : 0;
}
// Drop peer used to reconnect
void ClientChannel::dropReconnPeer(const char* reason)
{
String tmp;
getReconnPeer(tmp);
if (tmp)
ClientDriver::dropChan(tmp,reason);
}
/**
* ClientDriver
*/
ClientDriver::ClientDriver()
: Driver("client","misc")
{
s_driver = this;
}
ClientDriver::~ClientDriver()
{
s_driver = 0;
}
// install relays
void ClientDriver::setup()
{
Driver::setup();
Engine::install(new EngineStartHandler);
installRelay(Halt);
installRelay(Progress);
installRelay(Route,200);
installRelay(Text);
installRelay(MsgExecute);
}
// if we receive a message for an incoming call, we pass the message on
// to the callIncoming function to handle it
bool ClientDriver::msgExecute(Message& msg, String& dest)
{
Debug(this,DebugInfo,"msgExecute() '%s'",dest.c_str());
return Client::self() && Client::self()->callIncoming(msg,dest);
}
// Timer notification
void ClientDriver::msgTimer(Message& msg)
{
Driver::msgTimer(msg);
// Tell the client to tick the logigs if busy
if (isBusy())
Client::setLogicsTick();
}
// Routing handler
bool ClientDriver::msgRoute(Message& msg)
{
// don't route here our own messages
if (name() == msg[YSTRING("module")])
return false;
String* routeType = msg.getParam(YSTRING("route_type"));
if (routeType) {
if (*routeType == YSTRING("msg")) {
if (!(Client::self() && Client::self()->imRouting(msg)))
return false;
msg.retValue() = name() + "/*";
return true;
}
if (*routeType != YSTRING("call"))
return Driver::msgRoute(msg);
}
if (Client::self() && Client::self()->callRouting(msg)) {
msg.retValue() = name() + "/*";
return true;
}
return Driver::msgRoute(msg);
}
bool ClientDriver::received(Message& msg, int id)
{
if (id == MsgExecute || id == Text) {
if (Client::isClientMsg(msg))
return false;
return Client::self() && Client::self()->imExecute(msg);
}
if (id == Halt) {
dropCalls();
if (Client::self())
Client::self()->quit();
}
return Driver::received(msg,id);
}
// Set/reset the active channel
bool ClientDriver::setActive(const String& id)
{
Lock lock(this);
bool ok = false;
// Hold the old one
if (m_activeId && m_activeId != id) {
ClientChannel* chan = findChan(m_activeId);
ok = chan && chan->setActive(false);
TelEngine::destruct(chan);
}
m_activeId = "";
// Select the new one
if (!id)
return ok;
ClientChannel* chan = findChan(id);
ok = chan && chan->setActive(true);
TelEngine::destruct(chan);
if (ok)
m_activeId = id;
return ok;
}
// find a channel with the specified line
ClientChannel* ClientDriver::findLine(int line)
{
if (line < 1)
return 0;
Lock mylock(this);
ObjList* l = &channels();
for (; l; l = l->next()) {
ClientChannel* cc = static_cast<ClientChannel*>(l->get());
if (cc && (cc->line() == line))
return cc;
}
return 0;
}
// Drop all calls belonging to the active driver
void ClientDriver::dropCalls(const char* reason)
{
Message m("call.drop");
if (!reason && Engine::exiting())
reason = "shutdown";
m.addParam("reason",reason,false);
if (self())
self()->dropAll(m);
}
// Attach/detach client channels peers' source/consumer
bool ClientDriver::setAudioTransfer(const String& id, const String& target)
{
DDebug(s_driver,DebugInfo,"setAudioTransfer(%s,%s)",id.c_str(),target.safe());
// Get master (id) and its peer
ClientChannel* master = findChan(id);
if (!master)
return false;
CallEndpoint* masterPeer = master->getPeer();
if (!(masterPeer && masterPeer->ref()))
masterPeer = 0;
// Release conference or transfer
String tmp = master->transferId();
if (master->conference())
setConference(id,false);
else if (master->transferId())
master->setTransfer();
// First remove any slave's transfer
ClientChannel* slave = findChan(tmp);
if (slave && !slave->conference()) {
setAudioTransfer(slave->id());
if (masterPeer) {
CallEndpoint* slavePeer = slave->getPeer();
if (slavePeer && slavePeer->ref()) {
DDebug(s_driver,DebugAll,"setAudioTransfer detaching peers for %s - %s",
master->id().c_str(),slave->id().c_str());
DataTranslator::detachChain(masterPeer->getSource(),slavePeer->getConsumer());
DataTranslator::detachChain(slavePeer->getSource(),masterPeer->getConsumer());
TelEngine::destruct(slavePeer);
}
}
}
TelEngine::destruct(slave);
// Set new transfer: we must have a valid target
bool ok = true;
CallEndpoint* slavePeer = 0;
while (target) {
ok = false;
if (!masterPeer)
break;
slave = findChan(target);
if (!slave)
break;
if (slave->conference())
break;
slavePeer = slave->getPeer();
if (!(slavePeer && slavePeer->ref())) {
slavePeer = 0;
break;
}
// The new target may be involved in a transfer
if (slave->transferId())
setAudioTransfer(target);
DDebug(s_driver,DebugAll,"setAudioTransfer attaching peers for %s - %s",
master->id().c_str(),slave->id().c_str());
ok = DataTranslator::attachChain(masterPeer->getSource(),slavePeer->getConsumer()) &&
DataTranslator::attachChain(slavePeer->getSource(),masterPeer->getConsumer());
// Fallback on failure
if (!ok) {
DataTranslator::detachChain(masterPeer->getSource(),slavePeer->getConsumer());
DataTranslator::detachChain(slavePeer->getSource(),masterPeer->getConsumer());
}
break;
}
// Set channels on success
if (target) {
if (ok) {
master->setTransfer(slave->id());
slave->setTransfer(master->id());
}
else
Debug(s_driver,DebugNote,
"setAudioTransfer failed to attach peers for %s - %s",
master->id().c_str(),target.c_str());
}
// Release references
TelEngine::destruct(slavePeer);
TelEngine::destruct(slave);
TelEngine::destruct(masterPeer);
TelEngine::destruct(master);
return ok;
}
// Attach/detach a client channel to/from a conference room
bool ClientDriver::setConference(const String& id, bool in, const String* confName,
bool buildFromChan)
{
Lock lock(s_driver);
if (!s_driver)
return false;
String dummy;
if (!confName) {
if (buildFromChan) {
dummy << "conf/" << id;
confName = &dummy;
}
else
confName = &s_confName;
}
DDebug(s_driver,DebugInfo,"setConference id=%s in=%s conf=%s",
id.c_str(),String::boolText(in),confName->c_str());
ClientChannel* chan = findChan(id);
if (!chan)
return false;
bool ok = false;
if (in) {
// Check if already in conference (or if the conference room is the same)
// Remove transfer
if (chan->conference()) {
if (chan->transferId() == *confName) {
TelEngine::destruct(chan);
return true;;
}
setConference(id,false);
}
else if (chan->transferId())
setAudioTransfer(id);
Message m("call.conference");
m.addParam("room",*confName);
m.addParam("notify",*confName);
m.addParam("maxusers",String(Client::s_maxConfPeers * 2));
m.userData(chan);
ok = Engine::dispatch(m);
if (ok)
chan->setConference(*confName);
else
Debug(s_driver,DebugNote,"setConference failed for '%s'",id.c_str());
}
else {
Message m("chan.locate");
m.addParam("id",chan->m_peerId);
Engine::dispatch(m);
CallEndpoint* cp = 0;
if (m.userData())
cp = static_cast<CallEndpoint*>(m.userData()->getObject(YATOM("CallEndpoint")));
const char* reason = "Unable to locate peer";
if (cp) {
ok = chan->connect(cp,"Conference terminated");
if (ok)
chan->setConference();
else
reason = "Connect failed";
}
if (!ok)
Debug(s_driver,DebugNote,"setConference failed to re-connect '%s'. %s",
id.c_str(),reason);
}
TelEngine::destruct(chan);
return ok;
}
// Find a channel with the specified id
ClientChannel* ClientDriver::findChan(const String& id)
{
Lock lock(s_driver);
if (!s_driver)
return 0;
Channel* chan = s_driver->find(id);
return (chan && chan->ref()) ? static_cast<ClientChannel*>(chan) : 0;
}
// Get a referenced channel whose stored peer is the given one
ClientChannel* ClientDriver::findChanByPeer(const String& peer)
{
Lock lock(s_driver);
if (!s_driver)
return 0;
for (ObjList* o = s_driver->channels().skipNull(); o; o = o->skipNext()) {
ClientChannel* c = static_cast<ClientChannel*>(o->get());
if (c && c->m_peerId == peer)
return c->ref() ? c : 0;
}
return 0;
}
// Drop a channel
void ClientDriver::dropChan(const String& chan, const char* reason, bool peer)
{
if (!peer) {
Message* m = Client::buildMessage("call.drop",String::empty());
m->addParam("id",chan);
m->addParam("reason",reason,false);
Engine::enqueue(m);
return;
}
ClientChannel* cc = ClientDriver::findChan(chan);
if (cc)
cc->dropReconnPeer(reason);
TelEngine::destruct(cc);
}
/**
* ClientAccount
*/
// Constructor
ClientAccount::ClientAccount(const char* proto, const char* user, const char* host,
bool startup, ClientContact* contact)
: Mutex(true,"ClientAccount"),
m_params(""), m_resource(0), m_contact(0)
{
m_params.addParam("enabled",String::boolText(startup));
m_params.addParam("protocol",proto,false);
m_params.addParam("username",user,false);
m_params.addParam("domain",host,false);
setResource(new ClientResource(m_params.getValue(YSTRING("resource"))));
setContact(contact);
Debug(ClientDriver::self(),DebugAll,"Created client account='%s' [%p]",
toString().c_str(),this);
}
// Constructor. Build an account from a list of parameters.
ClientAccount::ClientAccount(const NamedList& params, ClientContact* contact)
: Mutex(true,"ClientAccount"),
m_params(params), m_resource(0), m_contact(0)
{
setResource(new ClientResource(m_params.getValue(YSTRING("resource"))));
setContact(contact);
Debug(ClientDriver::self(),DebugAll,"Created client account='%s' [%p]",
toString().c_str(),this);
}
// Set account own contact
void ClientAccount::setContact(ClientContact* contact)
{
Lock lock(this);
if (m_contact == contact)
return;
if (m_contact)
m_contact->m_owner = 0;
TelEngine::destruct(m_contact);
m_contact = contact;
if (m_contact) {
m_contact->m_owner = this;
m_contact->setSubscription("both");
}
}
// Get this account's resource
ClientResource* ClientAccount::resource(bool ref)
{
Lock lock(this);
if (!m_resource)
return 0;
return (!ref || m_resource->ref()) ? m_resource : 0;
}
// Set this account's resource
void ClientAccount::setResource(ClientResource* res)
{
if (!res)
return;
Lock lock(this);
if (res == m_resource)
return;
TelEngine::destruct(m_resource);
m_resource = res;
}
// Save this account to client accounts file or remove it
bool ClientAccount::save(bool ok, bool savePwd)
{
static const String s_oldId("old_id");
bool changed = false;
// Handle id changes (new version generate an internal id)
String old = m_params[s_oldId];
NamedList* oldSect = old ? Client::s_accounts.getSection(old) : 0;
if (oldSect) {
changed = true;
Client::s_accounts.clearSection(old);
}
m_params.clearParam(s_oldId);
NamedList* sect = Client::s_accounts.getSection(toString());
if (ok) {
if (!sect)
sect = Client::s_accounts.createSection(toString());
if (sect) {
changed = true;
*sect = m_params;
if (!savePwd)
sect->clearParam(YSTRING("password"));
// Don't save internal (temporary parameters)
sect->clearParam(YSTRING("internal"),'.');
sect->assign(toString());
}
}
else if (sect) {
changed = true;
Client::s_accounts.clearSection(toString());
}
if (!changed)
return true;
bool saved = Client::save(Client::s_accounts);
if (ok && !saved)
m_params.addParam("old_id",old,false);
return saved;
}
// Find a contact by its id
ClientContact* ClientAccount::findContact(const String& id, bool ref)
{
if (!id)
return 0;
Lock lock(this);
ClientContact* c = 0;
if (!m_contact || id != m_contact->toString()) {
ObjList* o = m_contacts.find(id);
c = o ? static_cast<ClientContact*>(o->get()) : 0;
}
else
c = m_contact;
return c && (!ref || c->ref()) ? c : 0;
}
// Find a contact by name and/or uri
ClientContact* ClientAccount::findContact(const String* name, const String* uri,
const String* skipId, bool ref)
{
if (!(name || uri))
return 0;
Lock lock(this);
for (ObjList* o = m_contacts.skipNull(); o; o = o->skipNext()) {
ClientContact* c = static_cast<ClientContact*>(o->get());
if ((skipId && *skipId == c->toString()) ||
(name && *name != c->m_name) || (uri && *uri != c->uri()))
continue;
return (!ref || c->ref()) ? c : 0;
}
return 0;
}
// Find a contact having a given id and resource
ClientContact* ClientAccount::findContact(const String& id, const String& resid,
bool ref)
{
Lock lock(this);
ClientContact* c = findContact(id,false);
if (!(c && c->findResource(resid)))
return 0;
return (!ref || c->ref()) ? c : 0;
}
// Find a contact by its URI (build an id from account and uri)
ClientContact* ClientAccount::findContactByUri(const String& uri, bool ref)
{
if (!uri)
return 0;
Lock lock(this);
String id;
ClientContact::buildContactId(id,toString(),uri);
return findContact(id,ref);
}
// Find a MUC room by its id
MucRoom* ClientAccount::findRoom(const String& id, bool ref)
{
if (!id)
return 0;
Lock lock(this);
ObjList* o = m_mucs.find(id);
if (!o)
return 0;
MucRoom* r = static_cast<MucRoom*>(o->get());
return (!ref || r->ref()) ? r : 0;
}
// Find a MUC room by its uri
MucRoom* ClientAccount::findRoomByUri(const String& uri, bool ref)
{
Lock lock(this);
String id;
ClientContact::buildContactId(id,toString(),uri);
return findRoom(id,ref);
}
// Find any contact (regular or MUC room) by its id
ClientContact* ClientAccount::findAnyContact(const String& id, bool ref)
{
ClientContact* c = findContact(id,ref);
return c ? c : findRoom(id,ref);
}
// Build a contact and append it to the list
ClientContact* ClientAccount::appendContact(const String& id, const char* name,
const char* uri)
{
Lock lock(this);
if (!id || findContact(id))
return 0;
ClientContact* c = new ClientContact(this,id,name,uri);
return c;
}
// Build a contact and append it to the list
ClientContact* ClientAccount::appendContact(const NamedList& params)
{
Lock lock(this);
if (params.null() || findContact(params))
return 0;
ClientContact* c = new ClientContact(this,params);
return c;
}
// Remove a contact from list. Reset contact's owner
ClientContact* ClientAccount::removeContact(const String& id, bool delObj)
{
Lock lock(this);
ClientContact* c = findContact(id);
if (!c)
c = findRoom(id);
if (!c || c == m_contact)
return 0;
c->m_owner = 0;
bool regular = !c->mucRoom();
if (regular)
m_contacts.remove(c,false);
else
m_mucs.remove(c,false);
lock.drop();
Debug(ClientDriver::self(),DebugAll,
"Account(%s) removed %s '%s' uri='%s' delObj=%u [%p]",
toString().c_str(),regular ? "contact" : "MUC room",
c->toString().c_str(),c->uri().c_str(),delObj,this);
if (delObj)
TelEngine::destruct(c);
return c;
}
// Clear MUC rooms
void ClientAccount::clearRooms(bool saved, bool temp)
{
if (!(saved || temp))
return;
Lock lock(this);
ListIterator iter(m_mucs);
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
MucRoom* r = static_cast<MucRoom*>(gen);
if (r->local() || r->remote()) {
if (saved)
m_mucs.remove(r);
}
else if (temp)
m_mucs.remove(r);
}
}
// Build a login/logout message from account's data
Message* ClientAccount::userlogin(bool login, const char* msg)
{
Message* m = Client::buildMessage(msg,toString(),login ? "login" : "logout");
if (login) {
m->copyParams(m_params);
m->clearParam(YSTRING("internal"),'.');
}
else
m->addParam("protocol",protocol(),false);
return m;
}
// Build a message used to update or query account userdata
Message* ClientAccount::userData(bool update, const String& data, const char* msg)
{
Message* m = Client::buildMessage(msg,toString(),update ? "update" : "query");
m->addParam("data",data,false);
if (!update || data != YSTRING("chatrooms"))
return m;
m->setParam("data.count","0");
unsigned int n = 0;
Lock lock(this);
for (ObjList* o = m_mucs.skipNull(); o; o = o->skipNext()) {
MucRoom* r = static_cast<MucRoom*>(o->get());
if (!r->remote())
continue;
String prefix;
prefix << "data." << ++n;
m->addParam(prefix,r->uri());
prefix << ".";
m->addParam(prefix + "name",r->m_name,false);
if (r->m_password) {
Base64 b((void*)r->m_password.c_str(),r->m_password.length());
String tmp;
b.encode(tmp);
m->addParam(prefix + "password",tmp);
}
for (ObjList* o = r->groups().skipNull(); o; o = o->skipNext())
m->addParam(prefix + "group",o->get()->toString(),false);
NamedIterator iter(r->m_params);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
// Skip local/remote params
if (ns->name() != YSTRING("local") && ns->name() != YSTRING("remote") &&
!ns->name().startsWith("internal."))
m->addParam(prefix + ns->name(),*ns);
}
}
m->setParam("data.count",String(n));
return m;
}
// Fill a list used to update an account list item
void ClientAccount::fillItemParams(NamedList& list)
{
list.addParam("account",toString());
list.addParam("protocol",m_params.getValue(YSTRING("protocol")));
const char* sName = resource().statusName();
NamedString* status = new NamedString("status",sName);
status->append(resource().m_text,": ");
list.addParam(status);
}
// Utility used in ClientAccount::setupDataDir
static bool showAccError(ClientAccount* a, String* errStr, const String& fail,
const char* what, int error = 0, const char* errorStr = 0)
{
String tmp;
if (!errStr)
errStr = &tmp;
if (error) {
Thread::errorString(*errStr,error);
*errStr = String(error) + " " + *errStr;
}
else
*errStr = errorStr;
*errStr = fail + " '" + what + "': " + *errStr;
Debug(ClientDriver::self(),DebugWarn,"Account(%s) %s [%p]",
a->toString().c_str(),errStr->c_str(),a);
return false;
}
// Set account data directory. Make sure it exists.
// Move all files from the old one if changed
bool ClientAccount::setupDataDir(String* errStr, bool saveAcc)
{
String dir;
String user = m_params[YSTRING("username")];
user.toLower();
String domain = m_params.getValue(YSTRING("domain"),m_params.getValue(YSTRING("server")));
domain.toLower();
dir << protocol().hash() << "_" << user.hash() << "_" << domain.hash();
if (dataDir() == dir) {
String s;
s << Engine::configPath(true) << Engine::pathSeparator() << dataDir();
ObjList d;
ObjList f;
File::listDirectory(s,&d,&f);
if (d.find(dataDir()))
return true;
if (f.find(dataDir()))
return showAccError(this,errStr,"Failed to create directory",s,0,
"A file with the same name already exists");
// Not found: clear old directory
m_params.clearParam(YSTRING("datadirectory"));
}
String path = Engine::configPath(true);
// Check if already there
int error = 0;
ObjList dirs;
ObjList files;
File::listDirectory(path,&dirs,&files,&error);
if (error)
return showAccError(this,errStr,"Failed to list directory",path,error);
String fullPath = path + Engine::pathSeparator() + dir;
if (files.find(dir))
return showAccError(this,errStr,"Failed to create directory",fullPath,0,
"A file with the same name already exists");
const String& existing = dataDir();
ObjList* oldDir = existing ? dirs.find(existing) : 0;
if (dirs.find(dir)) {
if (oldDir) {
// Move dirs and files from old directory
String old = path + Engine::pathSeparator() + existing;
ObjList all;
File::listDirectory(old,&all,&all,&error);
if (!error) {
bool ok = true;
for (ObjList* o = all.skipNull(); o; o = o->skipNext()) {
String* item = static_cast<String*>(o->get());
String oldItem = old + Engine::pathSeparator() + *item;
String newItem = fullPath + Engine::pathSeparator() + *item;
File::rename(oldItem,newItem,&error);
if (!error)
continue;
ok = false;
String tmp;
Thread::errorString(tmp,error);
Debug(ClientDriver::self(),DebugWarn,
"Account(%s) failed to move '%s' to '%s': %d %s [%p]",
toString().c_str(),oldItem.c_str(),newItem.c_str(),
error,tmp.c_str(),this);
error = 0;
}
// Delete it if all moved
if (ok) {
File::rmDir(old,&error);
if (error)
showAccError(this,errStr,"Failed to delete directory",old,error);
}
}
else
showAccError(this,errStr,"Failed to list directory",old,error);
}
}
else {
if (oldDir) {
// Rename it
String old = path + Engine::pathSeparator() + existing;
File::rename(old,fullPath,&error);
if (error)
return showAccError(this,errStr,"Failed to rename existing directory",
old,error);
}
else {
// Create a new one
File::mkDir(fullPath,&error);
if (error)
return showAccError(this,errStr,"Failed to create directory",
fullPath,error);
}
}
m_params.setParam("datadirectory",dir);
if (saveAcc) {
NamedList* sect = Client::s_accounts.getSection(toString());
if (sect) {
sect->setParam("datadirectory",dir);
Client::s_accounts.save();
}
}
// Set account meta data
loadDataDirCfg();
NamedList* sect = m_cfg.createSection("general");
sect->setParam("account",toString());
sect->copyParams(m_params,"protocol,username,domain,server");
m_cfg.save();
return true;
}
// Load configuration file from data directory
bool ClientAccount::loadDataDirCfg(Configuration* cfg, const char* file)
{
if (TelEngine::null(file))
return false;
if (!cfg)
cfg = &m_cfg;
if (!dataDir())
setupDataDir(0,false);
const String& dir = dataDir();
if (!dir)
return false;
*cfg = "";
*cfg << Engine::configPath(true) + Engine::pathSeparator() + dir;
*cfg << Engine::pathSeparator() << file;
return cfg->load();
}
// Clear account data directory
bool ClientAccount::clearDataDir(String* errStr)
{
if (!dataDir())
setupDataDir(0,false);
const String& dir = dataDir();
if (!dir)
return false;
// Check base path
String tmp(Engine::configPath(true));
ObjList dirs;
File::listDirectory(tmp,&dirs,0);
if (!dirs.find(dir))
return true;
// Delete files
tmp << Engine::pathSeparator() << dir;
int error = 0;
bool ok = false;
ObjList files;
if (File::listDirectory(tmp,0,&files,&error)) {
for (ObjList* o = files.skipNull(); o; o = o->skipNext()) {
String file(tmp + Engine::pathSeparator() + o->get()->toString());
int err = 0;
if (!File::remove(file,&err)) {
if (!error)
error = err;
}
}
if (!error)
ok = File::rmDir(tmp,&error);
}
return ok ? ok : showAccError(this,errStr,"Failed to clear data directory",tmp,error);
}
// Load contacts from configuration file
void ClientAccount::loadContacts(Configuration* cfg)
{
if (!cfg)
cfg = &m_cfg;
unsigned int n = cfg->sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = cfg->getSection(i);
if (!(sect && sect->c_str()))
continue;
const String& type = (*sect)[YSTRING("type")];
if (type == YSTRING("groupchat")) {
String id;
ClientContact::buildContactId(id,toString(),*sect);
MucRoom* room = findRoom(id);
if (!room)
room = new MucRoom(this,id,0,*sect);
room->groups().clear();
NamedIterator iter(*sect);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (ns->name() == YSTRING("type"))
continue;
if (ns->name() == YSTRING("name"))
room->m_name = *ns;
else if (ns->name() == YSTRING("password"))
room->m_password = *ns;
else if (ns->name() == YSTRING("group")) {
if (*ns)
room->appendGroup(*ns);
}
else
room->m_params.setParam(ns->name(),*ns);
}
room->setLocal(true);
Debug(ClientDriver::self(),DebugAll,
"Account(%s) loaded MUC room '%s' [%p]",
toString().c_str(),room->uri().c_str(),this);
}
}
}
// Remove from owner. Release data
void ClientAccount::destroyed()
{
lock();
TelEngine::destruct(m_resource);
TelEngine::destruct(m_contact);
// Clear contacts. Remove their owner before
for (ObjList* o = m_contacts.skipNull(); o; o = o->skipNext())
(static_cast<ClientContact*>(o->get()))->m_owner = 0;
m_contacts.clear();
for (ObjList* o = m_mucs.skipNull(); o; o = o->skipNext())
(static_cast<ClientContact*>(o->get()))->m_owner = 0;
m_mucs.clear();
unlock();
Debug(ClientDriver::self(),DebugAll,"Destroyed client account=%s [%p]",
toString().c_str(),this);
RefObject::destroyed();
}
// Method used by the contact to append itself to this account's list
void ClientAccount::appendContact(ClientContact* contact, bool muc)
{
if (!contact)
return;
Lock lock(this);
if (!muc)
m_contacts.append(contact);
else
m_mucs.append(contact);
contact->m_owner = this;
Debug(ClientDriver::self(),DebugAll,
"Account(%s) added contact '%s' name='%s' uri='%s' muc=%s [%p]",
toString().c_str(),contact->toString().c_str(),contact->m_name.c_str(),
contact->uri().c_str(),String::boolText(muc),this);
}
/**
* ClientAccountList
*/
// Destructor
ClientAccountList::~ClientAccountList()
{
TelEngine::destruct(m_localContacts);
}
// Check if a contact is locally stored
bool ClientAccountList::isLocalContact(ClientContact* c) const
{
return m_localContacts && c && c->account() == m_localContacts;
}
// Find an account
ClientAccount* ClientAccountList::findAccount(const String& id, bool ref)
{
Lock lock(this);
if (m_localContacts && m_localContacts->toString() == id)
return (!ref || m_localContacts->ref()) ? m_localContacts : 0;
if (!id)
return 0;
ObjList* o = m_accounts.find(id);
if (o) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
return (!ref || a->ref()) ? a : 0;
}
return 0;
}
// Find an account's contact by its URI
ClientContact* ClientAccountList::findContactByUri(const String& account, const String& uri,
bool ref)
{
Lock lock(this);
ClientAccount* acc = findAccount(account,false);
return acc ? acc->findContactByUri(uri,ref) : 0;
}
// Find an account's contact
ClientContact* ClientAccountList::findContact(const String& account, const String& id,
bool ref)
{
Lock lock(this);
ClientAccount* acc = findAccount(account,false);
return acc ? acc->findContact(id,ref) : 0;
}
// Find an account's contact from a built id
ClientContact* ClientAccountList::findContact(const String& builtId, bool ref)
{
String account;
ClientContact::splitContactId(builtId,account);
return findContact(account,builtId,ref);
}
// Find a contact an instance id
ClientContact* ClientAccountList::findContactByInstance(const String& id, String* instance,
bool ref)
{
String account,contact;
ClientContact::splitContactInstanceId(id,account,contact,instance);
return findContact(account,contact,ref);
}
// Find a MUC room by its id
MucRoom* ClientAccountList::findRoom(const String& id, bool ref)
{
String account;
ClientContact::splitContactId(id,account);
Lock lock(this);
ClientAccount* acc = findAccount(account);
return acc ? acc->findRoom(id,ref) : 0;
}
// Find a MUC room by member id
MucRoom* ClientAccountList::findRoomByMember(const String& id, bool ref)
{
String account,contact;
ClientContact::splitContactInstanceId(id,account,contact);
Lock lock(this);
ClientAccount* acc = findAccount(account);
return acc ? acc->findRoom(contact,ref) : 0;
}
// Find any contact (regular or MUC room) by its id
ClientContact* ClientAccountList::findAnyContact(const String& id, bool ref)
{
String account;
ClientContact::splitContactId(id,account);
Lock lock(this);
ClientAccount* acc = findAccount(account);
return acc ? acc->findAnyContact(id,ref) : 0;
}
// Check if there is a single registered account and return it
ClientAccount* ClientAccountList::findSingleRegAccount(const String* skipProto, bool ref)
{
Lock lock(this);
ClientAccount* found = 0;
for (ObjList* o = m_accounts.skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (!a->resource().online() || (skipProto && *skipProto == a->protocol()))
continue;
if (!found)
found = a;
else {
found = 0;
break;
}
}
return (found && (!ref || found->ref())) ? found : 0;
}
// Append a new account
bool ClientAccountList::appendAccount(ClientAccount* account)
{
if (!account || findAccount(account->toString()) || !account->ref())
return false;
m_accounts.append(account);
DDebug(ClientDriver::self(),DebugAll,"List(%s) added account '%s'",
c_str(),account->toString().c_str());
return true;
}
// Remove an account
void ClientAccountList::removeAccount(const String& id)
{
Lock lock(this);
ObjList* obj = m_accounts.find(id);
if (!obj)
return;
DDebug(ClientDriver::self(),DebugAll,"List(%s) removed account '%s'",
c_str(),obj->get()->toString().c_str());
obj->remove();
}
/**
* ClientContact
*/
// Constructor. Append itself to the owner's list
ClientContact::ClientContact(ClientAccount* owner, const char* id, const char* name,
const char* uri)
: m_name(name ? name : id), m_params(""), m_owner(owner), m_online(false),
m_uri(uri), m_dockedChat(false), m_share("")
{
m_dockedChat = Client::valid() && Client::self()->getBoolOpt(Client::OptDockedChat);
m_id = id ? id : uri;
XDebug(ClientDriver::self(),DebugAll,"ClientContact(%p) id=%s uri=%s [%p]",
owner,m_id.c_str(),m_uri.c_str(),this);
if (m_owner)
m_owner->appendContact(this);
updateShare();
// Generate chat window name
buildIdHash(m_chatWndName,s_chatPrefix);
}
// Constructor. Build a contact from a list of parameters.
ClientContact::ClientContact(ClientAccount* owner, const NamedList& params, const char* id,
const char* uri)
: m_name(params.getValue(YSTRING("name"),params)), m_params(""),
m_owner(owner), m_online(false), m_uri(uri), m_dockedChat(false), m_share("")
{
m_dockedChat = Client::valid() && Client::self()->getBoolOpt(Client::OptDockedChat);
m_id = id ? id : params.c_str();
XDebug(ClientDriver::self(),DebugAll,"ClientContact(%p) id=%s uri=%s [%p]",
owner,m_id.c_str(),m_uri.c_str(),this);
if (m_owner)
m_owner->appendContact(this);
updateShare();
// Generate chat window name
buildIdHash(m_chatWndName,s_chatPrefix);
}
// Constructor. Append itself to the owner's list
ClientContact::ClientContact(ClientAccount* owner, const char* id, bool mucRoom)
: m_params(""), m_owner(owner), m_online(false), m_id(id), m_dockedChat(false),
m_share("")
{
if (m_owner)
m_owner->appendContact(this,mucRoom);
if (!mucRoom) {
m_dockedChat = Client::valid() && Client::self()->getBoolOpt(Client::OptDockedChat);
buildIdHash(m_chatWndName,s_chatPrefix);
}
updateShare();
}
// Set contact's subscription
bool ClientContact::setSubscription(const String& value)
{
if (m_subscription == value)
return false;
m_subscription = value;
m_sub = 0;
if (m_subscription == YSTRING("both"))
m_sub = SubFrom | SubTo;
else if (m_subscription == YSTRING("from"))
m_sub = SubFrom;
else if (m_subscription == YSTRING("to"))
m_sub = SubTo;
return true;
}
// Check if this contact has a chat widget (window or docked item)
bool ClientContact::hasChat()
{
Window* w = getChatWnd();
if (!w)
return false;
if (m_dockedChat)
return Client::self()->getTableRow(s_dockedChatWidget,toString(),0,w);
return true;
}
// Flash chat window/item to notify the user
void ClientContact::flashChat(bool on)
{
Window* w = getChatWnd();
if (!w)
return;
if (on)
Client::self()->setUrgent(w->id(),true,w);
if (m_dockedChat)
flashItem(on,s_dockedChatWidget,toString(),w);
}
// Send chat to contact (enqueue a msg.execute message)
bool ClientContact::sendChat(const char* body, const String& res,
const String& type, const char* state)
{
Message* m = Client::buildMessage("msg.execute",accountName());
m->addParam("type",type,false);
m->addParam("called",m_uri);
m->addParam("called_instance",res,false);
m->addParam("body",body);
if (mucRoom())
m->addParam("muc",String::boolText(true));
if (!TelEngine::null(state) && (!type || type == YSTRING("chat") || type == YSTRING("groupchat")))
m->addParam("chatstate",state);
return Engine::enqueue(m);
}
// Retrieve the contents of the chat input widget
void ClientContact::getChatInput(String& text, const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
if (m_dockedChat) {
NamedList p("");
p.addParam(name,"");
Client::self()->getTableRow(s_dockedChatWidget,toString(),&p,w);
text = p[name];
}
else
Client::self()->getText(name,text,false,w);
}
// Set the chat input widget text
void ClientContact::setChatInput(const String& text, const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
if (m_dockedChat) {
NamedList p("");
p.addParam(name,text);
Client::self()->setTableRow(s_dockedChatWidget,toString(),&p,w);
}
else
Client::self()->setText(name,text,false,w);
}
// Retrieve the contents of the chat history widget
void ClientContact::getChatHistory(String& text, bool richText, const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
if (m_dockedChat) {
String param;
if (richText)
param << "getrichtext:";
param << name;
NamedList p("");
p.addParam(param,"");
Client::self()->getTableRow(s_dockedChatWidget,toString(),&p,w);
text = p[param];
}
else
Client::self()->getText(name,text,richText,w);
}
// Set the contents of the chat history widget
void ClientContact::setChatHistory(const String& text, bool richText, const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
if (m_dockedChat) {
NamedList p("");
if (richText)
p.addParam("setrichtext:" + name,text);
else
p.addParam(name,text);
Client::self()->setTableRow(s_dockedChatWidget,toString(),&p,w);
}
else
Client::self()->setText(name,text,richText,w);
}
// Add an entry to chat history
void ClientContact::addChatHistory(const String& what, NamedList*& params, const String& name)
{
Window* w = getChatWnd();
if (!(w && name && params)) {
TelEngine::destruct(params);
return;
}
NamedList* lines = new NamedList("");
lines->addParam(new NamedPointer(what,params,String::boolText(true)));
if (m_dockedChat) {
NamedList p("");
p.addParam(new NamedPointer("addlines:" + name,lines));
Client::self()->setTableRow(s_dockedChatWidget,toString(),&p,w);
}
else {
Client::self()->addLines(name,lines,0,false,w);
TelEngine::destruct(lines);
}
params = 0;
}
// Retrieve a chat widget' property
void ClientContact::getChatProperty(const String& name, const String& prop,
String& value)
{
Window* w = getChatWnd();
if (!(w && name && prop))
return;
if (m_dockedChat) {
String param;
param << "property:" << name << ":" << prop;
NamedList p("");
p.addParam(param,"");
Client::self()->getTableRow(s_dockedChatWidget,toString(),&p,w);
value = p[param];
}
else
Client::self()->getProperty(name,prop,value,w);
}
// Set a chat widget' property
void ClientContact::setChatProperty(const String& name, const String& prop,
const String& value)
{
Window* w = getChatWnd();
if (!(w && name && prop))
return;
if (m_dockedChat) {
NamedList p("");
p.addParam("property:" + name + ":" + prop,value);
Client::self()->setTableRow(s_dockedChatWidget,toString(),&p,w);
}
else
Client::self()->setProperty(name,prop,value,w);
}
// Show or hide this contact's chat window or docked item
bool ClientContact::showChat(bool visible, bool active)
{
Window* w = getChatWnd();
if (!w)
return false;
if (!visible) {
if (m_dockedChat)
return Client::self()->delTableRow(s_dockedChatWidget,toString(),w);
return Client::self()->setVisible(m_chatWndName,false);
}
bool ok = Client::self()->getVisible(w->id()) || Client::self()->setVisible(w->id(),true);
if (active) {
if (m_dockedChat)
Client::self()->setSelect(s_dockedChatWidget,toString(),w);
Client::self()->setActive(w->id(),true,w);
}
return ok;
}
// Get the chat window
Window* ClientContact::getChatWnd()
{
if (!Client::valid())
return 0;
if (mucRoom())
return Client::self()->getWindow(s_mucsWnd);
if (m_dockedChat)
return Client::self()->getWindow(s_dockedChatWnd);
return Client::self()->getWindow(m_chatWndName);
}
// Create the chat window
void ClientContact::createChatWindow(bool force, const char* name)
{
if (force)
destroyChatWindow();
if (hasChat())
return;
if (!Client::valid())
return;
if (m_dockedChat) {
Window* w = getChatWnd();
if (!w)
return;
Client::self()->addTableRow(s_dockedChatWidget,toString(),0,false,w);
return;
}
if (TelEngine::null(name))
name = s_chatPrefix;
Client::self()->createWindowSafe(name,m_chatWndName);
Window* w = getChatWnd();
if (!w)
return;
NamedList p("");
p.addParam("context",toString());
updateChatWindow(p);
}
// Update the chat window
void ClientContact::updateChatWindow(const NamedList& params, const char* title,
const char* icon)
{
Window* w = getChatWnd();
if (!w)
return;
if (m_dockedChat) {
Client::self()->setTableRow(s_dockedChatWidget,toString(),&params,w);
return;
}
NamedList p(params);
p.addParam("title",title,false);
p.addParam("image:" + m_chatWndName,icon,false);
Client::self()->setParams(&p,w);
}
// Check if the contact chat is active
bool ClientContact::isChatActive()
{
Window* w = getChatWnd();
if (!w)
return false;
// We are in the client's thread: use the Window's method
if (!w->active())
return false;
if (!m_dockedChat)
return true;
String sel;
Client::self()->getSelect(s_dockedChatWidget,sel,w);
return sel == toString();
}
// Close the chat window or destroy docked chat item
void ClientContact::destroyChatWindow()
{
Window* w = getChatWnd();
if (!w)
return;
if (m_dockedChat)
Client::self()->delTableRow(s_dockedChatWidget,toString(),w);
else
Client::self()->closeWindow(m_chatWndName,false);
}
// Find a group this contact might belong to
String* ClientContact::findGroup(const String& group)
{
Lock lock(m_owner);
ObjList* obj = m_groups.find(group);
return obj ? static_cast<String*>(obj->get()) : 0;
}
// Append a group to this contact
bool ClientContact::appendGroup(const String& group)
{
Lock lock(m_owner);
if (findGroup(group))
return false;
m_groups.append(new String(group));
DDebug(ClientDriver::self(),DebugAll,
"Account(%s) contact='%s' added group '%s' [%p]",
m_owner ? m_owner->toString().c_str() : "",m_uri.c_str(),group.c_str(),this);
return true;
}
// Remove a contact's group
bool ClientContact::removeGroup(const String& group)
{
Lock lock(m_owner);
ObjList* obj = m_groups.find(group);
if (!obj)
return false;
obj->remove();
DDebug(ClientDriver::self(),DebugAll,
"Account(%s) contact='%s' removed group '%s' [%p]",
m_owner ? m_owner->toString().c_str() : "",m_uri.c_str(),group.c_str(),this);
return true;
}
// Replace contact's groups from a list of parameters (handle 'group' parameters)
bool ClientContact::setGroups(const NamedList& list, const String& param)
{
Lock lock(m_owner);
ObjList* grps = 0;
NamedIterator iter(list);
const NamedString* ns = 0;
while (0 != (ns = iter.get())) {
if (ns->name() != param)
continue;
if (!grps)
grps = new ObjList;
grps->append(new String(*ns));
}
if (grps) {
bool changed = false;
String oldGrps, newGrps;
oldGrps.append(m_groups,",");
newGrps.append(grps,",");
changed = (oldGrps != newGrps);
if (changed) {
m_groups.clear();
for (ObjList* o = grps->skipNull(); o; o = o->skipNext())
appendGroup(o->get()->toString());
}
TelEngine::destruct(grps);
return changed;
}
if (m_groups.skipNull()) {
m_groups.clear();
return true;
}
return false;
}
// Find the resource with the lowest status
ClientResource* ClientContact::status(bool ref)
{
ClientResource* res = 0;
for (ObjList* o = resources().skipNull(); o; o = o->skipNext()) {
ClientResource* r = static_cast<ClientResource*>(o->get());
if (!res || res->m_status > r->m_status)
res = r;
if (res->m_status == ClientResource::Online)
break;
}
return (res && (!ref || res->ref())) ? res : 0;
}
// Find a resource having a given id
ClientResource* ClientContact::findResource(const String& id, bool ref)
{
Lock lock(m_owner);
ObjList* obj = m_resources.find(id);
if (!obj)
return 0;
ClientResource* r = static_cast<ClientResource*>(obj->get());
return (!ref || r->ref()) ? r : 0;
}
// Get the first resource with audio capability
ClientResource* ClientContact::findAudioResource(bool ref)
{
Lock lock(m_owner);
ObjList* o = m_resources.skipNull();
for (; o; o = o->skipNext())
if ((static_cast<ClientResource*>(o->get()))->caps().flag(ClientResource::CapAudio))
break;
if (!o)
return 0;
ClientResource* r = static_cast<ClientResource*>(o->get());
return (!ref || r->ref()) ? r : 0;
}
// Get the first resource with file transfer capability capability
ClientResource* ClientContact::findFileTransferResource(bool ref)
{
Lock lock(m_owner);
ObjList* o = m_resources.skipNull();
for (; o; o = o->skipNext())
if ((static_cast<ClientResource*>(o->get()))->caps().flag(ClientResource::CapFileTransfer))
break;
if (!o)
return 0;
ClientResource* r = static_cast<ClientResource*>(o->get());
return (!ref || r->ref()) ? r : 0;
}
// Append a resource having a given id
ClientResource* ClientContact::appendResource(const String& id)
{
if (findResource(id))
return 0;
ClientResource* r = new ClientResource(id);
if (!insertResource(r))
TelEngine::destruct(r);
return r;
}
// Insert a resource in the list by its priority.
// If the resource is already there it will be extracted and re-inserted
bool ClientContact::insertResource(ClientResource* res)
{
if (!res || findResource(res->toString()))
return false;
ObjList* found = m_resources.find(res);
if (found)
found->remove(false);
// Insert it
ObjList* o = m_resources.skipNull();
for (; o; o = o->skipNext()) {
ClientResource* r = static_cast<ClientResource*>(o->get());
if (r->m_priority < res->m_priority)
break;
}
if (o)
o->insert(res);
else
m_resources.append(res);
if (!found)
DDebug(ClientDriver::self(),DebugAll,
"Account(%s) contact='%s' added resource '%s' prio=%d [%p]",
accountName().c_str(),m_uri.c_str(),
res->toString().c_str(),res->m_priority,this);
return true;
}
// Remove a resource having a given id
bool ClientContact::removeResource(const String& id)
{
Lock lock(m_owner);
ObjList* obj = m_resources.find(id);
if (!obj)
return false;
obj->remove();
DDebug(ClientDriver::self(),DebugAll,
"Account(%s) contact='%s' removed resource '%s' [%p]",
m_owner ? m_owner->toString().c_str() : "",m_uri.c_str(),id.c_str(),this);
return true;
}
// (re)load shared list
void ClientContact::updateShare()
{
m_share.clearParams();
if (!(account() && uri()))
return;
NamedList* sect = account()->m_cfg.getSection("share " + uri());
if (!sect)
return;
for (int n = 1; true; n++) {
String s(n);
NamedString* ns = sect->getParam(s);
if (!ns)
break;
if (!*ns)
continue;
setShareDir((*sect)[s + ".name"],*ns,false);
}
}
// Save share list
void ClientContact::saveShare()
{
if (!(account() && uri()))
return;
String tmp;
tmp << "share " << uri();
NamedList* sect = account()->m_cfg.getSection(tmp);
bool changed = false;
if (haveShare()) {
if (!sect)
sect = account()->m_cfg.createSection(tmp);
sect->clearParams();
NamedIterator iter(m_share);
int n = 1;
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
String s(n++);
sect->addParam(s,ns->name());
if (*ns && *ns != ns->name())
sect->addParam(s + ".name",*ns);
}
changed = true;
}
else if (sect) {
changed = true;
account()->m_cfg.clearSection(tmp);
}
if (!changed)
return;
if (account()->m_cfg.save())
return;
int code = Thread::lastError();
String s;
Thread::errorString(s,code);
Debug(ClientDriver::self(),DebugNote,
"Account(%s) contact='%s' failed to save shared: %d %s [%p]",
m_owner ? m_owner->toString().c_str() : "",m_uri.c_str(),code,s.c_str(),this);
}
// Clear share list
void ClientContact::clearShare()
{
if (!haveShare())
return;
m_share.clearParams();
saveShare();
}
// Set a share directory
bool ClientContact::setShareDir(const String& shareName, const String& dirPath, bool save)
{
String path;
if (!Client::removeEndsWithPathSep(path,dirPath))
return false;
String name = shareName;
if (!name)
Client::getLastNameInPath(name,path);
NamedString* ns = m_share.getParam(path);
NamedString* other = Client::findParamByValue(m_share,name,ns);
if (other)
return false;
bool changed = false;
if (ns) {
if (*ns != name) {
changed = true;
*ns = name;
}
}
else {
changed = true;
m_share.addParam(path,name);
}
if (changed && save)
saveShare();
return changed;
}
// Remove a shared item
bool ClientContact::removeShare(const String& name, bool save)
{
NamedString* ns = m_share.getParam(name);
if (!ns)
return false;
m_share.clearParam(ns);
if (save)
saveShare();
return true;
}
// Check if the list of shared contains something
bool ClientContact::haveShared() const
{
for (ObjList* o = m_shared.skipNull(); o; o = o->skipNext()) {
ClientDir* d = static_cast<ClientDir*>(o->get());
if (d->children().skipNull())
return true;
}
return false;
}
// Retrieve shared data for a given resource
ClientDir* ClientContact::getShared(const String& name, bool create)
{
if (!name)
return 0;
ObjList* o = m_shared.find(name);
if (!o && create)
o = m_shared.append(new ClientDir(name));
return o ? static_cast<ClientDir*>(o->get()) : 0;
}
// Remove shared data
bool ClientContact::removeShared(const String& name, ClientDir** removed)
{
bool chg = false;
if (name) {
GenObject* gen = m_shared.remove(name,false);
chg = (gen != 0);
if (removed)
*removed = static_cast<ClientDir*>(gen);
else
TelEngine::destruct(gen);
}
else {
chg = (0 != m_shared.skipNull());
m_shared.clear();
}
return chg;
}
// Split a contact instance id in account/contact/instance parts
void ClientContact::splitContactInstanceId(const String& src, String& account,
String& contact, String* instance)
{
int pos = src.find('|');
if (pos < 0) {
account = src.uriUnescape();
return;
}
account = src.substr(0,pos).uriUnescape();
int pp = src.find('|',pos + 1);
if (pp > pos) {
contact = src.substr(0,pp);
if (instance)
*instance = src.substr(pp + 1).uriUnescape();
}
else
contact = src;
}
// Remove from owner
void ClientContact::removeFromOwner()
{
if (!m_owner)
return;
Lock lock(m_owner);
m_owner->removeContact(m_id,false);
m_owner = 0;
}
// Remove from owner. Release data
void ClientContact::destroyed()
{
// Remove from owner now to make sure the contact is not deleted
// from owner while beeing destroyed
removeFromOwner();
if (!mucRoom() && Client::valid() &&
Client::self()->getBoolOpt(Client::OptDestroyChat))
destroyChatWindow();
RefObject::destroyed();
}
/*
* MucRoom
*/
// Constructor
MucRoom::MucRoom(ClientAccount* owner, const char* id, const char* name,
const char* uri, const char* nick)
: ClientContact(owner,id,true),
m_index(0),
m_resource(0)
{
String rid;
buildInstanceId(rid,m_id);
m_resource = new MucRoomMember(rid,nick);
m_name = name;
m_uri = uri;
if (!owner)
return;
if (owner->contact())
m_resource->m_uri = owner->contact()->uri();
m_resource->m_instance = owner->resource().toString();
}
// Check if the user can kick a given room member
bool MucRoom::canKick(MucRoomMember* member) const
{
if (!(member && available()) || ownMember(member))
return false;
return m_resource->m_role == MucRoomMember::Moderator &&
member->m_role > MucRoomMember::RoleNone &&
member->m_role < MucRoomMember::Moderator;
}
// Check if the user can ban a given room member
bool MucRoom::canBan(MucRoomMember* member) const
{
if (!(member && available()) || ownMember(member))
return false;
// Only admins and owners are allowed to ban non admin/owners
return m_resource->m_affiliation >= MucRoomMember::Admin &&
member->m_affiliation < MucRoomMember::Admin;
}
// Build a muc.room message used to login/logoff
Message* MucRoom::buildJoin(bool join, bool history, unsigned int sNewer)
{
Message* m = buildMucRoom(join ? "login" : "logout");
m->addParam("nick",m_resource->m_name,false);
if (!join)
return m;
m->addParam("password",m_password,false);
m->addParam("history",String::boolText(history));
if (history) {
if (sNewer)
m->addParam("history.newer",String(sNewer));
}
return m;
}
// Retrieve a room member (or own member) by its nick
MucRoomMember* MucRoom::findMember(const String& nick)
{
if (nick == m_resource->m_name)
return m_resource;
for (ObjList* o = m_resources.skipNull(); o; o = o->skipNext()) {
MucRoomMember* r = static_cast<MucRoomMember*>(o->get());
if (nick == r->m_name)
return r;
}
return 0;
}
// Retrieve a room member (or own member) by its contact and instance
MucRoomMember* MucRoom::findMember(const String& contact, const String& instance)
{
if (!(contact && instance))
return 0;
if (m_resource->m_instance == instance && (m_resource->m_uri &= contact))
return m_resource;
for (ObjList* o = m_resources.skipNull(); o; o = o->skipNext()) {
MucRoomMember* r = static_cast<MucRoomMember*>(o->get());
if (r->m_instance == instance && (r->m_uri &= contact))
return r;
}
return 0;
}
// Retrieve a room member (or own member) by its id
MucRoomMember* MucRoom::findMemberById(const String& id)
{
if (ownMember(id))
return m_resource;
return static_cast<MucRoomMember*>(findResource(id));
}
// Check if a given member has chat displayed
bool MucRoom::hasChat(const String& id)
{
Window* w = getChatWnd();
return w && Client::self()->getTableRow(s_dockedChatWidget,id,0,w);
}
// Flash chat window/item to notify the user
void MucRoom::flashChat(const String& id, bool on)
{
Window* w = getChatWnd();
if (!w)
return;
if (on)
Client::self()->setUrgent(w->id(),true,w);
flashItem(on,s_dockedChatWidget,id,w);
}
// Retrieve the contents of the chat input widget
void MucRoom::getChatInput(const String& id, String& text, const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
NamedList p("");
p.addParam(name,"");
Client::self()->getTableRow(s_dockedChatWidget,id,&p,w);
text = p[name];
}
// Set the chat input widget text
void MucRoom::setChatInput(const String& id, const String& text, const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
NamedList p("");
p.addParam(name,text);
Client::self()->setTableRow(s_dockedChatWidget,id,&p,w);
}
// Retrieve the contents of the chat history widget
void MucRoom::getChatHistory(const String& id, String& text, bool richText,
const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
String param;
if (richText)
param << "getrichtext:";
param << name;
NamedList p("");
p.addParam(param,"");
Client::self()->getTableRow(s_dockedChatWidget,id,&p,w);
text = p[param];
}
// Set the contents of the chat history widget
void MucRoom::setChatHistory(const String& id, const String& text, bool richText,
const String& name)
{
Window* w = getChatWnd();
if (!(w && name))
return;
NamedList p("");
if (richText)
p.addParam("setrichtext:" + name,text);
else
p.addParam(name,text);
Client::self()->setTableRow(s_dockedChatWidget,id,&p,w);
}
// Add an entry to chat history
void MucRoom::addChatHistory(const String& id, const String& what, NamedList*& params,
const String& name)
{
Window* w = getChatWnd();
if (!(w && name && params)) {
TelEngine::destruct(params);
return;
}
NamedList* lines = new NamedList("");
lines->addParam(new NamedPointer(what,params,String::boolText(true)));
NamedList p("");
p.addParam(new NamedPointer("addlines:" + name,lines));
Client::self()->setTableRow(s_dockedChatWidget,id,&p,w);
params = 0;
}
// Set a chat widget' property
void MucRoom::setChatProperty(const String& id, const String& name, const String& prop,
const String& value)
{
Window* w = getChatWnd();
if (!(w && name && prop))
return;
NamedList p("");
p.addParam("property:" + name + ":" + prop,value);
Client::self()->setTableRow(s_dockedChatWidget,id,&p,w);
}
// Show or hide a member's chat
bool MucRoom::showChat(const String& id, bool visible, bool active)
{
Window* w = getChatWnd();
if (!w)
return false;
if (!visible)
return Client::self()->delTableRow(s_dockedChatWidget,id,w);
bool ok = Client::self()->setVisible(w->id(),true);
if (active) {
Client::self()->setSelect(s_dockedChatWidget,id,w);
Client::self()->setActive(w->id(),true,w);
}
return ok;
}
// Create a member's chat
void MucRoom::createChatWindow(const String& id, bool force, const char* name)
{
if (force)
destroyChatWindow(id);
if (hasChat(id))
return;
if (!Client::valid())
return;
MucRoomMember* m = static_cast<MucRoomMember*>(findResource(id,true));
Window* w = m ? getChatWnd() : 0;
if (w) {
NamedList p("");
p.addParam("item_type",ownMember(m) ? "mucroom" : "mucprivchat");
Client::self()->addTableRow(s_dockedChatWidget,id,&p,false,w);
}
TelEngine::destruct(m);
}
// Update member parameters in chat window
void MucRoom::updateChatWindow(const String& id, const NamedList& params)
{
Window* w = getChatWnd();
if (w)
Client::self()->setTableRow(s_dockedChatWidget,id,&params,w);
}
// Check if the contact chat is active
bool MucRoom::isChatActive(const String& id)
{
Window* w = getChatWnd();
if (!w)
return false;
// We are in the client's thread: use the Window's method
if (!w->active())
return false;
String sel;
Client::self()->getSelect(s_dockedChatWidget,sel,w);
return sel == id;
}
// Close a member's chat
void MucRoom::destroyChatWindow(const String& id)
{
Window* w = getChatWnd();
if (!w)
return;
if (id)
Client::self()->delTableRow(s_dockedChatWidget,id,w);
else {
NamedList tmp("");
tmp.addParam(m_resource->toString(),"");
for (ObjList* o = m_resources.skipNull(); o; o = o->skipNext())
tmp.addParam(o->get()->toString(),"");
Client::self()->updateTableRows(s_dockedChatWidget,&tmp,false,w);
}
}
// Retrieve a room member (or own member) by its id
ClientResource* MucRoom::findResource(const String& id, bool ref)
{
ClientResource* res = 0;
if (m_resource->toString() == id)
res = static_cast<ClientResource*>(m_resource);
else
res = ClientContact::findResource(id,false);
return (res && (!ref || res->ref())) ? res : 0;
}
// Append a member having a given nick
ClientResource* MucRoom::appendResource(const String& nick)
{
if (!nick || findMember(nick))
return 0;
String id;
buildInstanceId(id,String(++m_index));
MucRoomMember* m = new MucRoomMember(id,nick);
m_resources.append(m);
return static_cast<ClientResource*>(m);
}
// Remove a contact having a given nick
bool MucRoom::removeResource(const String& nick, bool delChat)
{
MucRoomMember* member = findMember(nick);
if (!member || ownMember(member))
return false;
if (delChat)
destroyChatWindow(member->toString());
m_resources.remove(member);
return true;
}
// Release data
void MucRoom::destroyed()
{
Debug(ClientDriver::self(),DebugAll,"MucRoom(%s) account=%s destroyed [%p]",
uri().c_str(),accountName().c_str(),this);
if (!m_resource->offline() && m_owner)
Engine::enqueue(buildJoin(false));
// Remove from owner now to make sure the contact is not deleted
// from owner while beeing destroyed
removeFromOwner();
destroyChatWindow();
TelEngine::destruct(m_resource);
ClientContact::destroyed();
}
/**
* ClientSound
*/
// Start playing the file
bool ClientSound::start(bool force)
{
if (m_started && !force)
return true;
stop();
DDebug(ClientDriver::self(),DebugInfo,"Starting sound %s",c_str());
m_started = doStart();
if (!m_started)
Debug(ClientDriver::self(),DebugNote,"Failed to start sound %s",c_str());
return m_started;
}
// Stop playing the file
void ClientSound::stop()
{
if (!m_started)
return;
DDebug(ClientDriver::self(),DebugInfo,"Stopping sound %s",c_str());
doStop();
m_started = false;
}
// Set/reset channel on sound start/stop
void ClientSound::setChannel(const String& chan, bool ok)
{
// Reset
if (!ok) {
if (m_channel && m_channel == chan)
doStop();
return;
}
// Set
if (m_started) {
if (m_channel == chan)
return;
else
doStop();
}
m_channel = chan;
m_started = true;
}
// Attach this sound to a channel
bool ClientSound::attachSource(ClientChannel* chan)
{
if (!chan)
return false;
Message* m = new Message("chan.attach");
m->userData(chan);
m->addParam("source",s_calltoPrefix + file());
m->addParam("autorepeat",String::boolText(m_repeat != 1));
return Engine::enqueue(m);
}
// Check if a sound is started
bool ClientSound::started(const String& name)
{
if (!name)
return false;
Lock lock(s_soundsMutex);
ObjList* obj = s_sounds.find(name);
return obj ? (static_cast<ClientSound*>(obj->get()))->started() : false;
}
// Create a sound
bool ClientSound::build(const String& id, const char* file, const char* device,
unsigned int repeat, bool resetExisting, bool stereo)
{
if (!id)
return false;
Lock lock(s_soundsMutex);
ClientSound* s = find(id);
if (s) {
if (resetExisting) {
s->file(file,stereo);
s->device(device);
s->setRepeat(repeat);
}
return false;
}
s = new ClientSound(id,file,device);
s->setRepeat(repeat);
s->m_stereo = stereo;
s_sounds.append(s);
DDebug(ClientDriver::self(),DebugAll,"Created sound '%s' file=%s device=%s [%p]",
id.c_str(),file,device,s);
return true;
}
// Start playing a given sound
bool ClientSound::start(const String& name, bool force)
{
if (!name)
return false;
Lock lock(s_soundsMutex);
ObjList* obj = s_sounds.find(name);
if (!obj)
return false;
return (static_cast<ClientSound*>(obj->get()))->start(force);
}
// Stop playing a given sound
void ClientSound::stop(const String& name)
{
if (!name)
return;
Lock lock(s_soundsMutex);
ObjList* obj = s_sounds.find(name);
if (!obj)
return;
(static_cast<ClientSound*>(obj->get()))->stop();
}
// Find a sound object
ClientSound* ClientSound::find(const String& token, bool byName)
{
if (!token)
return 0;
Lock lock(s_soundsMutex);
if (byName) {
ObjList* obj = s_sounds.find(token);
return obj ? static_cast<ClientSound*>(obj->get()) : 0;
}
// Find by file
for (ObjList* o = s_sounds.skipNull(); o; o = o->skipNext()) {
ClientSound* sound = static_cast<ClientSound*>(o->get());
if (token == sound->file())
return sound;
}
return 0;
}
bool ClientSound::doStart()
{
if (file().null())
return false;
Message m("call.execute");
m.addParam("callto",s_calltoPrefix + file());
ClientChannel* chan = new ClientChannel(toString());
chan->initChan();
m.userData(chan);
m.addParam("autorepeat",String::boolText(m_repeat != 1));
TelEngine::destruct(chan);
return Engine::dispatch(m);
}
void ClientSound::doStop()
{
if (m_channel) {
ClientChannel* chan = ClientDriver::findChan(m_channel);
if (chan)
chan->disconnect();
TelEngine::destruct(chan);
}
m_channel = "";
m_started = false;
}
//
// ClientDir
//
// Recursively check if all (sub)directores were updated
bool ClientDir::treeUpdated() const
{
if (!m_updated)
return false;
for (ObjList* o = m_children.skipNull(); o; o = o->skipNext()) {
ClientFileItem* it = static_cast<ClientFileItem*>(o->get());
ClientDir* dir = it->directory();
if (dir && !dir->treeUpdated())
return false;
}
return true;
}
// Build and add a sub-directory if not have one already
ClientDir* ClientDir::addDir(const String& name)
{
if (!name)
return 0;
ClientFileItem* it = findChild(name);
if (it) {
if (it->directory())
return it->directory();
}
ClientDir* d = new ClientDir(name);
addChild(d);
return d;
}
// Build sub directories from path
ClientDir* ClientDir::addDirPath(const String& path, const char* sep)
{
if (!path)
return 0;
if (TelEngine::null(sep))
return addDir(path);
int pos = path.find(sep);
if (pos < 0)
return addDir(path);
String rest = path.substr(pos + 1);
String name = path.substr(0,pos);
ClientDir* d = this;
if (name)
d = addDir(name);
if (!d)
return 0;
return rest ? d->addDirPath(rest) : d;
}
// Add a copy of known children types
void ClientDir::copyChildren(const ObjList& list)
{
for (ObjList* o = list.skipNull(); o; o = o->skipNext()) {
ClientFileItem* item = static_cast<ClientFileItem*>(o->get());
if (item->file())
addChild(new ClientFile(*item->file()));
else if (item->directory())
addChild(new ClientDir(*item->directory()));
}
}
// Add a list of children, consume the objects
void ClientDir::addChildren(ObjList& list)
{
for (ObjList* o = list.skipNull(); o; o = o->skipNull()) {
ClientFileItem* item = static_cast<ClientFileItem*>(o->remove(false));
addChild(item);
}
}
// Add/replace an item
bool ClientDir::addChild(ClientFileItem* item)
{
if (!item)
return false;
ObjList* last = 0;
for (ObjList* o = m_children.skipNull(); o; o = o->skipNext()) {
ClientFileItem* it = static_cast<ClientFileItem*>(o->get());
if (it == item)
return true;
if (it->name() == item->name()) {
o->remove();
o->append(item);
return true;
}
ObjList* tmp = o->skipNext();
if (!tmp) {
last = o;
break;
}
o = tmp;
}
if (last)
last->append(item);
else
m_children.append(item);
return true;
}
// Find a child by path
ClientFileItem* ClientDir::findChild(const String& path, const char* sep)
{
if (!path)
return 0;
if (TelEngine::null(sep))
return findChildName(path);
int pos = path.find(sep);
if (pos < 0)
return findChildName(path);
String rest = path.substr(pos + 1);
String name = path.substr(0,pos);
if (!name)
return findChild(rest,sep);
ClientFileItem* ch = findChildName(name);
if (!ch)
return 0;
// Nothing more in the path, return found child
if (!name)
return ch;
// Found child can't contain children: error
ClientDir* d = ch->directory();
if (d)
return d->findChild(rest,sep);
return 0;
}
//
// NamedInt
//
// Add an item to a list. Replace existing item with the same name
void NamedInt::addToListUniqueName(ObjList& list, NamedInt* obj)
{
if (!obj)
return;
ObjList* last = &list;
for (ObjList* o = list.skipNull(); o; o = o->skipNext()) {
NamedInt* ni = static_cast<NamedInt*>(o->get());
if (*ni == *obj) {
o->set(obj);
return;
}
last = o;
}
last->append(obj);
}
// Clear all items with a given value
void NamedInt::clearValue(ObjList& list, int val)
{
for (ObjList* o = list.skipNull(); o;) {
NamedInt* ni = static_cast<NamedInt*>(o->get());
if (ni->value() != val)
o = o->skipNext();
else {
o->remove();
o = o->skipNull();
}
}
}
/* vi: set ts=8 sw=4 sts=4 noet: */