yate/engine/ClientLogic.cpp

12527 lines
389 KiB
C++

/**
* ClientLogic.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Default client logic
*
* 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"
namespace TelEngine {
// A client wizard
class ClientWizard : public String
{
public:
ClientWizard(const String& wndName, ClientAccountList* accounts, bool temp = false);
// Check if a given window is the wizard
inline bool isWindow(Window* w)
{ return w && w->id() == toString(); }
// Retrieve the wizard window
inline Window* window() const
{ return Client::valid() ? Client::self()->getWindow(toString()) : 0; }
// Retrieve the account
inline ClientAccount* account()
{ return (m_accounts && m_account) ? m_accounts->findAccount(m_account) : 0; }
// Start the wizard. Show the window
virtual void start() {
reset(true);
changePage(String::empty());
Client::self()->setVisible(toString(),true,true);
}
virtual void reset(bool full)
{}
// Handle actions from wizard window. Return true if handled
virtual bool action(Window* w, const String& name, NamedList* params = 0);
// Handle checkable widgets status changes in wizard window
// Return true if handled
virtual bool toggle(Window* w, const String& name, bool active);
// Handle selection changes notifications. Return true if handled
virtual bool select(Window* w, const String& name, const String& item,
const String& text = String::empty())
{ return false; }
// Handle user.notify messages. Restart the wizard if the operating account is offline
// Return true if handled
virtual bool handleUserNotify(const String& account, bool ok, const char* reason = 0);
// Widgets
static const String s_pagesWidget; // Wizard pages UI widget
static const String s_actionNext; // The name of the 'next' action
static const String s_actionPrev; // The name of the 'previous' action
static const String s_actionCancel; // The name of the 'cancel async operation' action
protected:
virtual void onNext()
{}
virtual void onPrev()
{}
virtual void onCancel()
{}
// Wizard window visibility changed notification.
virtual void windowVisibleChanged(bool visible) {
if (!visible)
reset(true);
}
// Retrieve the current page from UI
inline void currentPage(String& page) const {
Window* w = window();
if (w)
Client::self()->getSelect(s_pagesWidget,page,w);
}
// Check if a given page is the current one
inline bool isCurrentPage(const String& page) const {
String p;
currentPage(p);
return p && p == page;
}
// Retrieve the selected account
ClientAccount* account(const String& list);
// Update wizard actions active status
void updateActions(NamedList& p, bool canPrev, bool canNext, bool canCancel);
// Change the wizard page
virtual bool changePage(const String& page, const String& old = String::empty())
{ return false; }
ClientAccountList* m_accounts; // The list of accounts if needed
String m_account; // The account used by the wizard
bool m_temp; // Wizard window is a temporary one
};
// New account wizard
// The accounts list object is not owned by the wizard
class AccountWizard : public ClientWizard
{
public:
inline AccountWizard(ClientAccountList* accounts)
: ClientWizard("accountwizard",accounts)
{}
~AccountWizard()
{ reset(true); }
virtual void reset(bool full);
virtual bool handleUserNotify(const String& account, bool ok, const char* reason = 0);
protected:
virtual void onNext();
virtual void onPrev();
virtual void onCancel();
virtual bool changePage(const String& page, const String& old = String::empty());
};
// Join MUC room wizard
// The accounts list object is not owned by the wizard
class JoinMucWizard : public ClientWizard
{
public:
// Build a join MUC wizard. Show the join page if temporary
JoinMucWizard(ClientAccountList* accounts, NamedList* tempParams = 0);
~JoinMucWizard()
{ reset(true); }
// Start the wizard. Show the window
virtual void start(bool add = false);
virtual void reset(bool full);
// Handle actions from wizard window. Return true if handled
virtual bool action(Window* w, const String& name, NamedList* params = 0);
// Handle selection changes notifications. Return true if handled
virtual bool select(Window* w, const String& name, const String& item,
const String& text = String::empty());
// Handle checkable widgets status changes in wizard window
// Return true if handled
virtual bool toggle(Window* w, const String& name, bool active);
// Process contact.info message
bool handleContactInfo(Message& msg, const String& account, const String& oper,
const String& contact);
// Handle user.notify messages. Update the accounts list
virtual bool handleUserNotify(const String& account, bool ok, const char* reason = 0);
protected:
virtual void onNext();
virtual void onPrev();
virtual void onCancel();
virtual bool changePage(const String& page, const String& old = String::empty());
// Handle the join room action
void joinRoom();
// Retrieve the selected MUC server if not currently requesting one
bool selectedMucServer(String* buf = 0);
// Set/reset servers query
void setQuerySrv(bool on, const char* domain = 0);
// Set/reset rooms query
void setQueryRooms(bool on, const char* domain = 0);
// Update UI progress params
void addProgress(NamedList& dest, bool on, const char* target);
// Update 'next' button status on select server page
void updatePageMucServerNext();
private:
bool m_add;
bool m_queryRooms; // Requesting rooms from server
bool m_querySrv; // Requesting MUC server(s)
ObjList m_requests; // Info/items requests id
String m_lastPage; // Last visited page
};
// Class holding an account status item and
// global account status data (the list of available status items)
class AccountStatus : public String
{
public:
inline AccountStatus(const char* name)
: String(name), m_status(ClientResource::Offline)
{}
inline int status() const
{ return m_status; }
inline const String& text() const
{ return m_text; }
// Retrieve current status item
static inline AccountStatus* current()
{ return s_current; }
// Find an item
static inline AccountStatus* find(const String& name) {
ObjList* o = s_items.find(name);
return o ? static_cast<AccountStatus*>(o->get()) : 0;
}
// Change the current item. Save to config if changed
// Return true if an item with the given name was found
static bool setCurrent(const String& name);
// Append/set an item. Save to config if changed
static void set(const String& name, int stat, const String& text, bool save = false);
// Load the list from config
static void load();
// Initialize the list
static void init();
// Update current status in UI
static void updateUi();
private:
static ObjList s_items; // Items
static AccountStatus* s_current; // Current status
int m_status;
String m_text;
};
// This class holds a pending request sent by the client
class PendingRequest : public RefObject
{
public:
enum Type {
SharedQuery,
};
inline PendingRequest(int type, const char* rid, const String& account, const String& target,
const String& instance = String::empty())
: m_id(rid), m_type(type), m_account(account), m_target(target),
m_instance(instance), m_msg(0), m_timeToSend(0) {
if (!rid)
buildId(m_id,type,account,target,instance,String::empty());
}
~PendingRequest()
{ TelEngine::destruct(m_msg); }
inline const String& requestId() const
{ return m_id; }
inline int type() const
{ return m_type; }
inline const String& account() const
{ return m_account; }
inline const String& target() const
{ return m_target; }
inline const String& instance() const
{ return m_instance; }
inline Message* buildMessageTo(const char* msg, const char* oper = 0) {
Message* m = Client::buildMessage(msg,account(),oper);
m->addParam("to",target(),false);
m->addParam("to_instance",instance(),false);
m->addParam("id",requestId(),false);
return m;
}
virtual const String& toString() const
{ return requestId(); }
// Set pending message and logics tick
inline bool setPendingMsg(Message* m, u_int64_t delayUs) {
if (!(m && delayUs))
return false;
m_msg = m;
m_timeToSend = m->msgTime() + delayUs;
Client::setLogicsTick();
return true;
}
// Send pending message. Return true if still pending and set logics tick
inline bool sendPendingMsg(const Time& time) {
if (!m_msg)
return false;
if (!m_timeToSend || m_timeToSend <= time) {
Engine::enqueue(m_msg);
m_msg = 0;
m_timeToSend = 0;
return false;
}
Client::setLogicsTick();
return true;
}
// Find an item. This method is not thread safe
static inline PendingRequest* find(const String& rid) {
ObjList* o = s_items.find(rid);
return o ? static_cast<PendingRequest*>(o->get()) : 0;
}
// Check if an item is in the list. This method is thread safe
static inline bool hasRequest(const String& rid) {
Lock lck(s_mutex);
return 0 != find(rid);
}
// Find an item. This method is thread safe
static inline bool find(const String& rid, RefPointer<PendingRequest>& r) {
Lock lck(s_mutex);
r = find(rid);
return r != 0;
}
// Safely remove an item
static inline void remove(const String& rid) {
Lock lck(s_mutex);
s_items.remove(rid);
}
// Remove all account's requests
static void clear(const String& account);
// Remove all contacts's requests
static void cancel(ClientContact* c, const String& res = String::empty());
// Build request id
static void buildIdNoType(String& buf, const String& acc, const String& target,
const String& res, const String& extra = String::empty(), bool addTime = true);
// Build request id
static inline void buildIdNoType(String& buf, ClientContact& c, const String& res,
const String& extra = String::empty(), bool addTime = true)
{ buildIdNoType(buf,c.accountName(),c.uri(),res,extra,addTime); }
// Build request id
static inline void buildId(String& buf, int type, const String& acc, const String& target,
const String& res, const String& extra = String::empty(), bool addTime = true) {
buf << type;
if (!acc)
return;
buf << "_";
buildIdNoType(buf,acc,target,res,extra,addTime);
}
// Start a request, consume the objects
static bool start(PendingRequest* r, Message* m, u_int64_t delayUs = 0);
static ObjList s_items;
static Mutex s_mutex;
private:
String m_id;
protected:
int m_type;
String m_account; // The account
String m_target; // Request target
String m_instance; // Target instance
Message* m_msg;
u_int64_t m_timeToSend;
};
class SharedPendingRequest : public PendingRequest
{
public:
inline SharedPendingRequest(const char* id, const String& account, const String& target,
const String& instance = String::empty())
: PendingRequest(SharedQuery,id,account,target,instance),
m_dir(true), m_index(0)
{}
// Build message for request
Message* buildMessage();
// Start the request
static inline bool start(ClientContact* c, ClientResource* res,
const String& what = String::empty(), bool dir = true,
unsigned int index = 0, u_int64_t delayUs = 0) {
return c && start(c->accountName(),c->uri(),
res ? res->toString() : String::empty(),what,dir,index,delayUs);
}
// Start the request
static bool start(const String& account, const String& contact, const String& inst,
const String& what = String::empty(), bool dir = true,
unsigned int index = 0, u_int64_t delayUs = 0);
bool m_dir;
String m_what;
unsigned int m_index;
};
// Chat state notificator
// This class is not thread safe. Data MUST be changed from client's thread
class ContactChatNotify : public String
{
public:
enum State {
None = 0,
Active,
Composing,
Paused,
Inactive,
};
// Update timers
inline void updateTimers(const Time& time) {
m_paused = time.msec() + s_pauseInterval;
m_inactive = time.msec() + s_inactiveInterval;
}
// Check for timeout. Reset the timer if a notification is returned
State timeout(Time& time);
// Send the notification
static void send(State state, ClientContact* c, MucRoom* room, MucRoomMember* member);
// Add or remove items from list. Notify active/composing if changed
// Don't notify active if empty and 'notify' is false
static void update(ClientContact* c, MucRoom* room, MucRoomMember* member,
bool empty, bool notify = true);
// Check timeouts. Send notifications
static bool checkTimeouts(ClientAccountList& list, Time& time);
// Clear list
static inline void clear()
{ s_items.clear(); }
// State names
static const TokenDict s_states[];
private:
inline ContactChatNotify(const String& id, bool mucRoom, bool mucMember,
const Time& time = Time())
: String(id), m_mucRoom(mucRoom), m_mucMember(mucMember),
m_paused(0), m_inactive(0)
{ updateTimers(time); }
static u_int64_t s_pauseInterval; // Interval to send paused notification
static u_int64_t s_inactiveInterval; // Interval to send gone notification
static ObjList s_items; // Item list
bool m_mucRoom; // Regular contact or muc room
bool m_mucMember; // Room member
u_int64_t m_paused; // Time to send paused
u_int64_t m_inactive; // Time to send gone
};
class FtDownloadFileJob;
class FtDownloadDirContentJob;
class FtManager;
class FTManagerTimer;
// Base class for file transfer jobs
class FtJob : public String
{
public:
enum State {
NotFound = 0,
Idle,
Pending, // Waiting for operation to start
Running, // Running
Finished
};
inline FtJob(const String& itemId)
: String(itemId),
m_state(Idle)
{}
virtual FtDownloadFileJob* downloadFileJob()
{ return 0; }
virtual FtDownloadDirContentJob* downloadDirContentJob()
{ return 0; }
virtual void drop();
virtual void destruct();
// Drop a list of jobs. Reset job's notify id and add it to upd if non 0
static unsigned int dropJobs(ObjList& jobs, int newState, NamedList* upd = 0);
int m_state;
String m_notifyId; // Progress notify id
String m_dropId;
private:
FtJob() {}
};
// A download file job
class FtDownloadDirContentJob : public FtJob
{
public:
inline FtDownloadDirContentJob(const String& localPath, const String& downloadPath,
const String& name)
: FtJob(localPath),
m_downloadPath(downloadPath), m_dir(name)
{}
virtual FtDownloadDirContentJob* downloadDirContentJob()
{ return this; }
virtual void drop();
String m_downloadPath;
ClientDir m_dir;
};
// A download dir content job
class FtDownloadFileJob : public FtJob
{
public:
inline FtDownloadFileJob(const String& localPath, const String& dName,
const NamedList& params)
: FtJob(localPath),
m_file(params)
{ m_file.assign(dName); }
virtual FtDownloadFileJob* downloadFileJob()
{ return this; }
NamedList m_file; // Download path along with file parameters
};
// Base class for file transfer items
class FtItem : public Mutex, public RefObject
{
public:
FtItem(FtManager* owner, const String& itemId, const String& acc,
const String& cUri, const String& inst);
inline bool match(const String& account,
const String& contact = String::empty())
{ return m_account == account && (!contact || m_contactUri == contact); }
virtual bool setOnline(bool online);
virtual void cancel() = 0;
virtual const String& toString() const
{ return m_id; }
protected:
virtual void destroyed();
String m_id;
FtManager* m_owner;
bool m_online;
String m_dbg;
String m_account;
String m_contactUri;
String m_instance;
String m_contactName;
String m_target;
String m_refreshWnd;
String m_refreshName;
};
// A list of batch download(ing) items
class DownloadBatch : public FtItem
{
public:
DownloadBatch(FtManager* owner, const String& itemId, const String& acc,
const String& cUri, const String& inst);
inline bool haveJobs()
{ return (0 != m_fileDownloads.skipNull()) || (0 != m_retrieve.skipNull()); }
// Add a shared item
void addItem(ClientFileItem& item, const String& path, const String& itemPath,
const String& refreshWnd, const String& refreshName);
// Timer tick handler. Return false to exit
bool timerTick(const Time& time = Time());
// Handle file transfer notifications
void handleFileTransferNotify(Message& msg, const String& notifyId);
// Handle file info responses
bool handleFileInfoRsp(const String& oper, NamedList& msg);
// Cancel a job, return true if found and removed
inline bool cancel(const String& jobId) {
lock();
FtJob* job = removeNotify(jobId);
unlock();
return cancelJob(job,false);
}
// Set contact/instance online
virtual bool setOnline(bool online);
// Cancel all running jobs, clear data
virtual void cancel();
protected:
virtual void destroyed();
// Find a job by notify id
inline ObjList* findNotify(const String& notifyId) const {
for (ObjList* o = m_fileDownloads.skipNull(); o; o = o->skipNext())
if ((static_cast<FtJob*>(o->get()))->m_notifyId == notifyId)
return o;
return 0;
}
// Find and remove job by notify id
inline FtJob* removeNotify(const String& notifyId) const {
ObjList* o = findNotify(notifyId);
return o ? static_cast<FtJob*>(o->remove(false)) : 0;
}
// Find a dir content refresh holder
ObjList* findDirContent(const String& key, bool byLocalPath,
ObjList* start = 0) const;
// Start file download, consume the pointer
bool startFileDownload(FtDownloadFileJob* file, NamedList& uiParams);
// Cancel a job, return true on success
bool cancelJob(FtJob*& job, bool finished);
// Add a shared item
void addItemName(ClientFileItem& item, const String& path,
const String& itemPath);
// Add a shared file
void addFileUnsafe(const String& localPath, const String& downloadPath,
const NamedList& params);
// Add a shared directory
void addDirUnsafe(ClientDir& dir, const String& localPath,
const String& downloadPath);
ObjList m_retrieve; // Directories waiting for update
ObjList m_fileDownloads; // File download jobs
unsigned int m_dirContentReqCount;
unsigned int m_dirContentReqMax;
u_int64_t m_timeout;
u_int64_t m_timeToDownload; // Time to start another file download
unsigned int m_donwloadIntervalMs; // Interval between downloads start
};
// File transfer manager
class FtManager : public String, public DebugEnabler, public Mutex
{
public:
FtManager(ClientAccountList* accounts, const char* name = 0);
~FtManager();
inline ClientAccountList* accounts()
{ return m_accounts; }
// Build a download id if possible
bool buildDownloadId(String& buf, const String& requestorId,
const String& requestId);
// Decrease the number of current downloads
inline void downloadTerminated() {
Lock lck(this);
if (m_downloadCount)
m_downloadCount--;
}
// Drop a job. Return true if found
bool cancelFileTransfer(const String& notifyId);
// Drop jobs for account/contact
void cancel(const String& account, const String& contact = String::empty());
// Cancel all jobs, stop timer tick thread
void cancel();
// Find a batch download
inline DownloadBatch* findDownloadBatch(const String& s) const {
ObjList* o = m_downloadBatch.find(s);
return o ? static_cast<DownloadBatch*>(o->get()) : 0;
}
// Find a batch download
bool findDownloadBatch(RefPointer<DownloadBatch>& d, const String& acc,
const String& contact, const String& inst);
// Find a batch download by notify id
bool findDownloadBatchNotify(RefPointer<DownloadBatch>& d,
const String& s);
// Add an item to batch downloads
void addShareDownload(const String& acc, const String& contact, const String& inst,
const String& item, const String& path,
const String& refreshWnd, const String& refreshName);
// Add an item to batch downloads
void addShareDownload(ClientContact& c, const String& inst, ClientFileItem& item,
const String& path, const String& itemPath,
const String& refreshWnd, const String& refreshName);
// Timer tick terminated notification
void timerTerminated(FTManagerTimer* timer);
// Timer tick handler. Return false to exit
bool timerTick(const Time& time = Time());
// Handle file transfer notifications
bool handleFileTransferNotify(Message& msg, const String& notifyId);
// Handle file info responses
bool handleFileInfoRsp(const String& account, const String& contact,
const String& inst, const String& oper, NamedList& msg);
// Handle resource.notify
void handleResourceNotify(bool online, const String& account,
const String& contact = String::empty(), const String& inst = String::empty());
// Check if a notification is a non job terminated one
static inline bool isRunningNotify(const NamedList& list)
{ return isRunningNotify(list[YSTRING("status")]); }
static inline bool isRunningNotify(const String& status)
{ return (status != YSTRING("terminated")) && (status != YSTRING("destroyed")); }
// Update file transfer items
static bool updateFileTransfers(NamedList& params, bool checkEmpty);
// Update a file transfer item
// addNew: true to add a new item if not found
static bool updateFileTransferItem(bool addNew, const String& id, NamedList& params,
bool setVisible = false, bool activate = false);
// Build file transfer item update data
static void buildFileTransferItem(NamedList& list, const String& notifyId, bool send,
const String& account, const String& contact, const String& inst, const String& cName,
const String& file, const String& chan);
// Add a file transfer item
static inline bool addFileTransferItem(NamedList& list, bool setVisible = true,
bool activate = true)
{ return updateFileTransferItem(true,list,list,setVisible,activate); }
// Add a file transfer item
static inline bool addFileTransferItem(const String& notifyId, bool send,
const String& account, const String& contact, const String& inst, const String& cName,
const String& file, const String& chan, bool setVisible = true, bool activate = true) {
NamedList p("");
buildFileTransferItem(p,notifyId,send,account,contact,inst,cName,file,chan);
return updateFileTransferItem(true,p,p,setVisible,activate);
}
// Update item progress
static bool updateFtProgress(const String& notifyId, NamedList& params);
// Update finished item
static bool updateFtFinished(const String& notifyId, NamedList& params, bool dropChan,
const String* file = 0, const String* contact = 0, bool* terminated = 0);
// Retrieve a file transfer item
// Delete the item from list. Drop the channel
static bool getFileTransferItem(const String& id, NamedList& params, Window* w = 0);
// Drop a file transfer item
// Delete the item from list. Drop the channel
static bool dropFileTransferItem(const String& id, const String* chan = 0,
bool hideEmpty = true);
// Hide file transfer empty file transfer window
static void hideEmptyFtWindow(Window* w = 0);
protected:
ClientAccountList* m_accounts;
unsigned int m_jobId;
FTManagerTimer* m_timer;
ObjList m_downloadBatch;
ListIterator m_downloadBatchIter;
bool m_downloadBatchChanged;
unsigned int m_downloadCount;
unsigned int m_downloadMax;
String m_downloadNotifyPrefix;
private:
void cancelTimer();
};
class FTManagerTimer : public Thread
{
public:
FTManagerTimer(FtManager* owner);
~FTManagerTimer();
virtual void run();
private:
void notify();
FtManager* m_owner;
};
}; // namespace TelEngine
using namespace TelEngine;
// Windows
static const String s_wndMain = "mainwindow"; // mainwindow
static const String s_wndAccount = "account"; // Account edit/add
static const String s_wndAddrbook = "addrbook"; // Contact edit/add
static const String s_wndChatContact = "chatcontact"; // Chat contact edit/add
static const String s_wndMucInvite = "mucinvite"; // MUC invite
static const String s_wndAcountList = "accountlist"; // Accounts list
static const String s_wndFileTransfer = "fileprogress"; // File transfer
static const String s_wndNotification = "notification"; // Notifications
// Some UI widgets
static const String s_mainwindowTabs = "mainwindowTabs";
static const String s_channelList = "channels";
static const String s_accountList = "accounts"; // Global accounts list
static const String s_contactList = "contacts";
static const String s_logList = "log";
static const String s_calltoList = "callto";
static const String s_account = "account"; // Account selector
static const String s_chatAccount = "chataccount"; // List of chat accounts
static const String s_chatContactList = "chat_contacts"; // List of chat contacts
static const String s_mucAccounts = "mucaccount"; // List of accounts supporting MUC
static const String s_mucSavedRooms = "mucsavedrooms"; // List of saved MUC rooms
static const String s_mucMembers = "muc_members"; // List of MUC room members
static const String s_accProtocol = "acc_protocol"; // List of protocols in account add/edit
static const String s_accWizProtocol = "accwiz_protocol"; // List of protocols in account wizard
static const String s_accProviders = "acc_providers"; // List of providers in account add/edit
static const String s_accWizProviders = "accwiz_providers"; // List of providers in account wizard
static const String s_inviteContacts = "invite_contacts"; // List of contacts in muc invite
static const String s_fileProgressList = "fileprogresslist"; // List of file transfers
static const String s_pageEmpty = "page_empty_list"; // An empty stacked widget page
static const String s_pageList = "page_list"; // A page for list in a stacked widget
static const String s_fileProgressCont = "file_progress_container"; // File progress window stacked widget
// Actions
static const String s_actionShowCallsList = "showCallsList";
static const String s_actionShowNotification = "showNotification";
static const String s_actionShowInfo = "showNotificationInfo";
static const String s_actionPendingChat = "showPendingChat";
static const String s_actionCall = "call";
static const String s_actionAnswer = "answer";
static const String s_actionHangup = "hangup";
static const String s_actionTransfer = "transfer";
static const String s_actionConf = "conference";
static const String s_actionHold = "hold";
static const String s_actionLogin = "acc_login";
static const String s_actionLogout = "acc_logout";
static const String s_chat = "chatcontact_chat";
static const String s_chatCall = "chatcontact_call";
static const String s_chatNew = "chatcontact_new";
static const String s_chatRoomNew = "chatroom_new";
static const String s_chatShowLog = "chatcontact_showlog";
static const String s_chatEdit = "chatcontact_edit";
static const String s_chatDel = "chatcontact_del";
static const String s_chatInfo = "chatcontact_info";
static const String s_chatSub = "chatcontact_subscribe";
static const String s_chatUnsubd = "chatcontact_unsubscribed";
static const String s_chatUnsub = "chatcontact_unsubscribe";
static const String s_chatShowOffline = "chatcontact_showoffline";
static const String s_chatFlatList = "chatcontact_flatlist";
static const String s_chatSend = "send_chat";
static const String s_fileSend = "send_file";
static const String s_fileShare = "share_file";
static const String s_fileShared = "shared_file";
static const String s_fileShareList = "share_file_list";
static const String s_fileSharedDirsList = "shared_dir_list";
static const String s_fileSharedDirsContent = "shared_dir_content";
static const String s_fileShareChooseDirPrefix = "share_file_choosedir:";
static const String s_fileLocalFs = "local_fs";
static const String s_mucJoin = "room_join";
static const String s_mucChgSubject = "room_changesubject";
static const String s_mucChgNick = "room_changenick";
static const String s_mucSave = "room_save";
static const String s_mucInvite = "room_invite_contacts";
static const String s_mucPrivChat = "room_member_chat";
static const String s_mucKick = "room_member_kick";
static const String s_mucBan = "room_member_ban";
static const String s_mucRoomShowLog = "room_showlog";
static const String s_mucMemberShowLog = "room_member_showlog";
static const String s_storeContact = "storecontact";
static const String s_mucInviteAdd = "invite_add";
static const String s_menuSubscription = "menuSubscription";
static const String s_fileShareNew = "file_share_new";
static const String s_fileShareDel = "file_share_del";
static const String s_fileShareRename = "file_share_rename";
// Not selected string(s)
static String s_notSelected = "-none-";
// Maximum number of call log entries
static unsigned int s_maxCallHistory = 20;
// Global account status
ObjList AccountStatus::s_items;
AccountStatus* AccountStatus::s_current = 0;
// Pending requests
ObjList PendingRequest::s_items;
Mutex PendingRequest::s_mutex(false,"PendingReq");
// Client wizard
const String ClientWizard::s_pagesWidget = "pages";
const String ClientWizard::s_actionNext = "next";
const String ClientWizard::s_actionPrev = "prev";
const String ClientWizard::s_actionCancel = "cancel";
// Wizards managed by the default logic
static AccountWizard* s_accWizard = 0;
static JoinMucWizard* s_mucWizard = 0;
// Chat state notificator
const TokenDict ContactChatNotify::s_states[] = {
{"active", Active},
{"composing", Composing},
{"paused", Paused},
{"inactive", Inactive},
{0,0}
};
u_int64_t ContactChatNotify::s_pauseInterval = 30000; // Paused notification
u_int64_t ContactChatNotify::s_inactiveInterval = 300000; // Inactive notification
ObjList ContactChatNotify::s_items; // Item list
// ClientLogic
ObjList ClientLogic::s_accOptions;
ObjList ClientLogic::s_protocols;
Mutex ClientLogic::s_protocolsMutex(true,"ClientProtocols");
// Parameters that are applied from provider template
const char* ClientLogic::s_provParams[] = {
"server",
"domain",
"outbound",
"port",
0
};
// Common account parameters (protocol independent)
static const String s_accParams[] = {
"username", "password", ""
};
// Common account boolean parameters (protocol independent)
static const String s_accBoolParams[] = {
"savepassword", ""
};
// Account protocol dependent parameters
static const String s_accProtoParams[] = {
"server", "domain", "outbound", "options", "resource", "port", "interval",
"authname", "authmethods", ""
};
// Account protocol dependent parameters set in lists (param=default_value)
static NamedList s_accProtoParamsSel("");
// Resource status images
static const TokenDict s_statusImage[] = {
{"status_offline.png", ClientResource::Offline},
{"status_connecting.png",ClientResource::Connecting},
{"status_online.png", ClientResource::Online},
{"status_busy.png", ClientResource::Busy},
{"status_dnd.png", ClientResource::Dnd},
{"status_away.png", ClientResource::Away},
{"status_xa.png", ClientResource::Xa},
{0,0}
};
// Saved rooms
static Configuration s_mucRooms;
// Actions from notification area
enum PrivateNotifAction {
PrivNotificationOk = 1,
PrivNotificationReject,
PrivNotificationLogin,
PrivNotificationAccEdit,
PrivNotificationAccounts,
PrivNotification1,
PrivNotification2,
PrivNotification3,
};
static const TokenDict s_notifPrefix[] = {
{"messages_ok:", PrivNotificationOk},
{"messages_reject:", PrivNotificationReject},
{"messages_login:", PrivNotificationLogin},
{"messages_acc_edit:", PrivNotificationAccEdit},
{"messages_accounts:", PrivNotificationAccounts},
{"messages_1:", PrivNotification1},
{"messages_2:", PrivNotification2},
{"messages_3:", PrivNotification3},
{0,0}
};
enum ChatLogEnum {
ChatLogSaveAll = 1,
ChatLogSaveUntilLogout,
ChatLogNoSave
};
// Archive save data
const TokenDict s_chatLogDict[] = {
{"chat_save_all", ChatLogSaveAll},
{"chat_save_untillogout", ChatLogSaveUntilLogout},
{"chat_nosave", ChatLogNoSave},
{0,0}
};
static ChatLogEnum s_chatLog = ChatLogSaveAll;
// Temporary wizards
static ObjList s_tempWizards;
// Chat state templates
static NamedList s_chatStates("");
// Changing docked chat state
static bool s_changingDockedChat = false;
// Pending chat items managed in the client's thread
static ObjList s_pendingChat;
// Google MUC domain
static const String s_googleMucDomain = "groupchat.google.com";
// Miscellaneous
static const String s_jabber = "jabber";
static const String s_sip = "sip";
static const String s_h323 = "h323";
static const String s_gmailDomain = "gmail.com";
static const String s_googleDomain = "google.com";
static const String s_fileOpenSendPrefix = "send_fileopen:";
static const String s_fileOpenRecvPrefix = "recv_fileopen:";
static String s_lastFileDir; // Last directory used to send/recv file
static String s_lastFileShareDir; // Last directory used to share files
static String s_lastFileFilter; // Last filter used to pick a file to send
static NamedList s_generic(""); // List for generic strings/data used across module
static unsigned int s_fileInfoMax = 20; // Max file info items to be returned in a single message
static String s_dirUp = "..";
// Dynamic load
static bool s_loadIAX = true;
// Check for protocol or target
// Load a module is needed
static void checkLoadModule(const NamedList* params, const String* target = 0)
{
if (!s_loadIAX)
return;
bool load = (target && target->startsWith("iax/")) ||
(params && (*params)[YSTRING("protocol")] == YSTRING("iax"));
if (!load)
return;
s_loadIAX = false;
Message m("engine.command");
m.addParam("line","module load yiaxchan.yate");
m.addParam("cmd_address","client");
Engine::dispatch(m);
const char* res = m.retValue();
if (res)
Output("%s",res);
}
static void copySubParams(NamedList& dest, const NamedList& src, const String& prefix,
const char* newPrefix, const String& skip = String::empty())
{
NamedIterator iter(src);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (!ns->name().startsWith(prefix))
continue;
if (!*ns)
continue;
String s = ns->name().substr(prefix.length());
if (s && (!skip || skip != s))
dest.addParam(newPrefix + s,*ns);
}
}
// Move a list into another one
static void moveList(ObjList& dest, ObjList& src)
{
for (ObjList* o = src.skipNull(); o; o = o->skipNext())
dest.append(o->remove(false));
src.clear();
}
// Retrieve the last item in a string list
static void addLastItem(NamedList& dest, const char* param, const char* value,
const String& src, char sep = *Engine::pathSeparator())
{
String v;
if (!value) {
Client::getLastNameInPath(v,src);
value = v.c_str();
}
dest.addParam(param,value);
}
// Decode file info items
static void decodeFileInfo(NamedList& list, ObjList& items, bool& complete)
{
static const String s_isFile = "isfile";
ObjList* last = &items;
for (int i = 1; true; i++) {
String prefix("item.");
prefix << i;
NamedString* ns = list.getParam(prefix);
if (!ns)
break;
if (!*ns)
continue;
prefix << ".";
ClientFileItem* item = 0;
if (list.getBoolValue(prefix + s_isFile)) {
ClientFile* f = new ClientFile(*ns);
copySubParams(f->params(),list,prefix,"file_",s_isFile);
item = f;
}
else
item = new ClientDir(*ns);
last = last->append(item);
}
complete = !list.getBoolValue(YSTRING("partial"));
}
// Utility: get contact from param or selected item
static ClientContact* getContactFromParam(ClientAccountList* accounts, NamedList* params,
const String& list, Window* wnd)
{
if (!accounts)
return 0;
String contact;
if (params)
contact = params->getValue(YSTRING("contact"));
if (!contact && Client::self())
Client::self()->getSelect(list,contact,wnd);
return contact ? accounts->findContact(contact) : 0;
}
// Utility: get contact from param or selected item or window context
static ClientContact* getContactFromParamContext(ClientAccountList* accounts, NamedList* params,
const String& list, Window* wnd)
{
if (!accounts)
return 0;
ClientContact* c = getContactFromParam(accounts,params,list,wnd);
if (!c && wnd && wnd->context())
c = accounts->findContact(wnd->context());
return c;
}
// Set the image parameter of a list
static inline void setImageParam(NamedList& p, const char* param,
const char* image, bool suffix = true)
{
static const String s_suffix = "_image";
static const String s_prefix = "image:";
if (suffix)
p.setParam(param + s_suffix,Client::s_skinPath + image);
else
p.setParam(s_prefix + param,Client::s_skinPath + image);
}
// Dump a list of parameters to output if XDEBUG is defined
static inline void dumpList(const NamedList& p, const char* text, Window* w = 0)
{
#ifdef XDEBUG
String tmp;
p.dump(tmp,"\r\n");
String wnd;
if (w)
wnd << " window=" << w->id();
Debug(ClientDriver::self(),DebugInfo,"%s%s\r\n-----\r\n%s\r\n-----",text,wnd.safe(),tmp.safe());
#endif
}
// Send a message used to remove all account share
static void removeAccountShareInfo(ClientAccount* a)
{
if (!a)
return;
Message* m = Client::buildMessage("file.info",a->toString(),"remove");
Engine::enqueue(m);
}
// Notify contact of shared changed
static void notifyContactShareInfoChanged(ClientContact* c)
{
if (!(c && c->subscriptionFrom()))
return;
for (ObjList* o = c->resources().skipNull(); o; o = o->skipNext()) {
ClientResource* res = static_cast<ClientResource*>(o->get());
if (!res->caps().flag(ClientResource::CapFileInfo))
continue;
Message* m = Client::buildMessage("file.info",c->accountName(),"notifychanged");
m->addParam("to",c->uri());
m->addParam("to_instance",res->toString());
Engine::enqueue(m);
}
}
// Utility used in updateContactShareInfo
static bool addContactShareInfo(NamedList& dest, ClientContact& c, bool set,
const String& prefix, const NamedString* item)
{
if (!item)
return false;
addLastItem(dest,prefix,*item,item->name());
if (set)
dest.addParam(prefix + ".path",item->name());
return true;
}
// Utility used in updateContactShareInfo
static bool addContactShareInfo(NamedList& dest, ClientContact& c, bool set,
const String& prefix, const NamedList& items)
{
bool ok = false;
unsigned int n = 1;
bool cShare = (&items == &(c.share()));
NamedIterator iter(items);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (cShare)
ok = addContactShareInfo(dest,c,set,prefix + "." + String(n++),ns) || ok;
else {
NamedString* item = c.share().getParam(ns->name());
if (item)
ok = addContactShareInfo(dest,c,set,prefix + "." + String(n++),item) || ok;
}
}
return ok;
}
// Send a message used to add a contact share
static void updateContactShareInfo(ClientContact* c, bool set, const String* item = 0,
const NamedList* items = 0)
{
static const String s_prefix("item");
if (!(c && c->uri()))
return;
if (set && !(c->subscriptionFrom() && c->haveShare()))
return;
Message* m = Client::buildMessage("file.info",c->accountName(),set ? "set" : "remove");
m->addParam("contact",c->uri());
bool ok = false;
if (item || items) {
if (item)
ok = addContactShareInfo(*m,*c,set,s_prefix,c->share().getParam(*item));
if (items)
ok = addContactShareInfo(*m,*c,set,s_prefix,*items);
}
else if (set)
ok = addContactShareInfo(*m,*c,true,s_prefix,c->share());
if (ok || set)
Engine::enqueue(m);
else
TelEngine::destruct(m);
}
// Send a message used to change/set a contact share
static bool changeContactShareInfo(ClientContact* c, const String& oldName,
const String& newName)
{
static const String s_prefix("item");
if (!(c && c->subscriptionFrom() && c->uri() && oldName && newName))
return false;
NamedString* ns = Client::findParamByValue(c->share(),newName);
if (!ns)
return false;
Message* m = Client::buildMessage("file.info",c->accountName(),"set");
m->addParam("contact",c->uri());
addContactShareInfo(*m,*c,true,s_prefix,ns);
m->addParam(s_prefix + ".oldname",oldName);
Engine::enqueue(m);
return true;
}
// Fill share status for contact
static void fillChatContactShareStatus(NamedList& p, ClientContact& c, bool global, bool chat)
{
if (chat) {
if (c.haveShare()) {
setImageParam(p,"chat_share_file","sharefile_20.png",false);
p.addParam("property:share_file_btn:_yate_normal_icon","sharefile_20.png");
p.addParam("property:share_file_btn:_yate_pressed_icon","sharefile_pressed_20.png");
p.addParam("property:share_file_btn:_yate_hover_icon","sharefile_hover_20.png");
}
else {
setImageParam(p,"chat_share_file","sharefile_none_20.png",false);
p.addParam("property:share_file_btn:_yate_normal_icon","sharefile_none_20.png");
p.addParam("property:share_file_btn:_yate_pressed_icon","sharefile_none_pressed_20.png");
p.addParam("property:share_file_btn:_yate_hover_icon","sharefile_none_hover_20.png");
}
}
}
// Show contact actions
void showChatContactActions(ClientContact& c, NamedList* list = 0)
{
if (!(list || Client::valid()))
return;
NamedString* ns = new NamedString("_yate_showactions");
if (c.haveShare())
ns->append(s_fileShare,",");
if (c.haveShared())
ns->append(s_fileShared,",");
if (list) {
list->addParam(ns);
return;
}
NamedList p("");
NamedList* contact = new NamedList(c.toString());
contact->addParam(ns);
p.addParam(new NamedPointer(c.toString(),contact,String::boolText(false)));
Client::self()->updateTableRows(s_chatContactList,&p,false);
}
// Update contact share status in UI
static void updateContactShareStatus(ClientContact& c)
{
showChatContactActions(c);
if (c.hasChat()) {
NamedList p("");
fillChatContactShareStatus(p,c,false,true);
c.updateChatWindow(p);
}
}
// Build shared dir item id
static void sharedBuildId(String& buf, ClientDir& baseDir, const String& path,
const String& item = String::empty())
{
buf = baseDir.name();
buf.uriEscape();
buf.append(path,"/");
buf.append(item,"/");
}
// Build shared dir item id
static void sharedSplitId(const String& buf, String& res, String& path)
{
int pos = buf.find('/');
if (pos >= 0) {
res = buf.substr(0,pos);
path = buf.substr(pos + 1);
}
else
res = buf;
}
// Build and shared item list used to update UI (dirs list or dir content)
static NamedList* sharedBuildUpdate(ClientContact* c, ClientDir& baseDir,
const String& path, const String& name, ClientFileItem* item = 0,
bool content = false)
{
String s;
sharedBuildId(s,baseDir,path,name);
bool isDirUp = content && (name == s_dirUp);
NamedList* p = new NamedList(s);
p->addParam("item_type",(isDirUp || !item || item->directory()) ? "dir" : "file");
if (!isDirUp)
p->addParam("name",path ? name : name + "@" + baseDir.name());
else
p->addParam("name",s_dirUp);
p->addParam("account",c->accountName());
p->addParam("contact",c->uri());
p->addParam("instance",baseDir.name());
p->addParam("path",path ? (path + "/" + name) : name);
if (path && !content) {
String parent;
sharedBuildId(parent,baseDir,path);
p->addParam("parent",parent);
}
return p;
}
// Build and add shared item(s) used to be shown in shared dirs
static void sharedDirsAddUpdate(NamedList& list, ClientContact* c, ClientDir* baseDir,
const String& path, ClientDir* dir, bool recursive)
{
if (!baseDir)
return;
if (!dir)
dir = baseDir;
for (ObjList* o = dir->children().skipNull(); o; o = o->skipNext()) {
ClientDir* d = (static_cast<ClientFileItem*>(o->get()))->directory();
if (!d)
continue;
NamedList* p = sharedBuildUpdate(c,*baseDir,path,d->name(),d);
list.addParam(new NamedPointer(*p,p,String::boolText(true)));
if (recursive) {
String tmp = path;
tmp.append(d->name(),"/");
sharedDirsAddUpdate(list,c,baseDir,tmp,d,true);
}
}
}
// Build and add shared item(s) used to be shown in shared dirs
// dir=0: Add the path
static void sharedDirsAddUpdate(NamedList& list, ClientContact* c, ClientDir* baseDir,
const String& path, ClientDir* dir = 0)
{
if (!baseDir)
return;
if (dir) {
NamedList* p = sharedBuildUpdate(c,*baseDir,path,dir->name(),dir);
list.addParam(new NamedPointer(*p,p,String::boolText(true)));
return;
}
// Add the path
if (!path)
return;
String tmpPath;
int oldPos = 0;
while (true) {
int pos = path.find('/',oldPos);
String name;
if (pos > oldPos) {
name = path.substr(oldPos,pos - oldPos);
oldPos = pos + 1;
}
else
name = path.substr(oldPos);
if (!name)
break;
NamedList* p = sharedBuildUpdate(c,*baseDir,tmpPath,name);
list.addParam(new NamedPointer(*p,p,String::boolText(true)));
tmpPath.append(name,"/");
if (pos < 0)
break;
}
}
// Update shared content in UI
// Request dirctory content if not updated
static void sharedContentUpdate(ClientContact* c, ClientDir* baseDir, const String& path,
ClientDir* dir, Window* w)
{
if (!(baseDir && dir))
return;
bool requesting = false;
if (!dir->updated() && c->subscriptionFrom())
requesting = SharedPendingRequest::start(c,c->findResource(baseDir->name()),path);
if (!(w && Client::valid()))
return;
// Show/hide busy
Client::self()->setBusy(s_fileSharedDirsContent,requesting,w);
if (requesting)
return;
// Display content
// Add 'up' directory
NamedList upd("");
if (path.find('/') >= 0) {
NamedList* p = sharedBuildUpdate(c,*baseDir,path,s_dirUp,0,true);
upd.addParam(new NamedPointer(*p,p,String::boolText(true)));
}
for (ObjList* o = dir->children().skipNull(); o ; o = o->skipNext()) {
ClientFileItem* item = static_cast<ClientFileItem*>(o->get());
NamedList* p = sharedBuildUpdate(c,*baseDir,path,item->name(),item,true);
upd.addParam(new NamedPointer(*p,p,String::boolText(true)));
}
Client::self()->updateTableRows(s_fileSharedDirsContent,&upd,false,w);
}
// Check reason and error for auth failure texts
static bool isNoAuth(const String& reason, const String& error)
{
static const String s_noAuth[] = {"noauth", "not-authorized", "invalid-authzid", ""};
for (int i = 0; s_noAuth[i]; i++)
if (reason == s_noAuth[i] || error == s_noAuth[i])
return true;
return false;
}
// Split user@domain
static inline void splitContact(const String& contact, String& user, String& domain)
{
int pos = contact.find('@');
if (pos >= 0) {
user = contact.substr(0,pos);
domain = contact.substr(pos + 1);
}
else
domain = contact;
}
// Utility: check if a string changed, set it, return true if changed
static inline bool setChangedString(String& dest, const String& src)
{
if (dest == src)
return false;
dest = src;
return true;
}
// Utility: check if a list parametr changed, set it, return true if changed
static inline bool setChangedParam(NamedList& dest, const String& param,
const String& src)
{
String* exist = dest.getParam(param);
if (exist)
return setChangedString(*exist,src);
dest.addParam(param,src);
return true;
}
// Append failure reason/error to a string
static void addError(String& buf, NamedList& list)
{
String* error = list.getParam(YSTRING("error"));
String* reason = list.getParam(YSTRING("reason"));
if (TelEngine::null(error)) {
if (TelEngine::null(reason))
return;
error = reason;
reason = 0;
}
buf.append(*error,": ");
if (!TelEngine::null(reason))
buf << " (" << *reason << ")";
}
// Build contact name: name <uri>
static inline void buildContactName(String& buf, ClientContact& c)
{
buf = c.m_name;
if (c.m_name != c.uri())
buf << " <" << c.uri() << ">";
}
// Compare list parameters given in array
// Return true if equal
static bool sameParams(const NamedList& l1, const NamedList& l2, const String* params)
{
if (!params)
return false;
while (*params && l1[*params] == l2[*params])
params++;
return params->null();
}
// Compare list parameters given in NamedList
// Return true if equal
static bool sameParams(const NamedList& l1, const NamedList& l2, const NamedList& params)
{
NamedIterator iter(params);
for (const NamedString* ns = 0; 0 != (ns = iter.get());)
if (l1[ns->name()] != l2[ns->name()])
return false;
return true;
}
// Build an user.login message
// Clear account password if not saved
static Message* userLogin(ClientAccount* a, bool login)
{
if (!a)
return 0;
Message* m = a->userlogin(login);
if (login && !a->params().getBoolValue(YSTRING("savepassword")))
a->m_params.clearParam(YSTRING("password"));
return m;
}
// Update filter param(s)
static void updateFilter(const String& name, Window* w, const String& text,
const char* param1, const char* param2 = 0)
{
NamedList tmp(name);
if (text) {
NamedList* filter = new NamedList("");
if (param1)
filter->addParam(param1,text);
if (param2)
filter->addParam(param2,text);
tmp.addParam(new NamedPointer("filter",filter));
}
else
tmp.addParam("filter","");
Client::self()->setParams(&tmp,w);
}
// Get items checked in a list
static ObjList* getEnabledCheckedItems(const String& list, Window* w)
{
if (!Client::self())
return 0;
ObjList* ret = 0;
NamedList tmp("");
Client::self()->getOptions(list,&tmp,w);
NamedIterator iter(tmp);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
NamedList p("");
Client::self()->getTableRow(list,ns->name(),&p,w);
if (p.getBoolValue(YSTRING("check:enabled"))) {
if (!ret)
ret = new ObjList;
ret->append(new String(ns->name()));
}
}
return ret;
}
// Check if a list has enabled checked items
static bool hasEnabledCheckedItems(const String& list, Window* w)
{
if (!Client::self())
return false;
NamedList tmp("");
Client::self()->getOptions(list,&tmp,w);
NamedIterator iter(tmp);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
NamedList p("");
Client::self()->getTableRow(list,ns->name(),&p,w);
if (p.getBoolValue(YSTRING("check:enabled")))
return true;
}
return false;
}
// Check if a contact is a local one
// Check if selected in an optional list
static bool isLocalContact(const String* item, ClientAccountList* accounts,
const String& checkSelList = String::empty())
{
if (!accounts)
return true;
ClientContact* c = 0;
if (item)
c = !item->null() ? accounts->findContactByInstance(*item) : 0;
else if (checkSelList) {
String sel;
Client::self()->getSelect(checkSelList,sel);
c = sel ? accounts->findContactByInstance(sel) : 0;
}
return c && accounts->isLocalContact(c);
}
// Retrieve a contact or MUC room from name:id.
// For MUC rooms the id is assumed to be a member id.
// Return true if the prefix was found
static bool getPrefixedContact(const String& name, const String& prefix, String& id,
ClientAccountList* list, ClientContact** c, MucRoom** room)
{
if (!(list && (room || c)))
return false;
int pos = name.find(':');
if (pos < 0 || name.substr(0,pos) != prefix)
return false;
id = name.substr(pos + 1);
if (c)
*c = list->findContact(id);
if (!(c && *c) && room)
*room = list->findRoomByMember(id);
return true;
}
// Check if a protocol is a telephony one
// FIXME: find a better way to detect it
static inline bool isTelProto(const String& proto)
{
return proto != s_jabber;
}
// Check if a given account is a gmail one
static inline bool isGmailAccount(ClientAccount* acc)
{
if (!(acc && acc->contact()))
return false;
return (acc->contact()->uri().getHost() &= s_gmailDomain) ||
(acc->contact()->uri().getHost() &= s_googleDomain);
}
// Check if a given account is tigase.im
static inline bool isTigaseImAccount(ClientAccount* acc)
{
static String s_tigaseIm = "tigase.im";
return acc && acc->contact() && (acc->contact()->uri().getHost() &= s_tigaseIm);
}
// Check if a given domain is a Google MUC server
static inline bool isGoogleMucDomain(const String& domain)
{
return (domain &= s_googleMucDomain);
}
// Retrieve protocol specific page name in UI
static const String& getProtoPage(const String& proto)
{
static const String s_default = "default";
static const String s_none = "none";
if (proto == s_jabber)
return s_jabber;
if (proto == s_sip)
return s_sip;
if (proto == s_h323)
return s_h323;
if (proto)
return s_default;
return s_none;
}
// Show a confirm dialog box in a given window
static bool showInput(Window* wnd, const String& name, const char* text,
const char* context, const char* title, const char* input = 0)
{
if (!(Client::valid() && name))
return false;
NamedList p("");
p.addParam("inputdialog_text",text);
p.addParam("inputdialog_input",input);
p.addParam("property:" + name + ":_yate_context",context);
return Client::self()->createDialog(YSTRING("input"),wnd,title,name,&p);
}
// Show a confirm dialog box in a given window
static bool showConfirm(Window* wnd, const char* text, const char* context)
{
static const String name = "confirm_dialog";
if (!Client::valid())
return false;
NamedList p("");
p.addParam("text",text);
p.addParam("property:" + name + ":_yate_context",context);
return Client::self()->createDialog(YSTRING("confirm"),wnd,String::empty(),name,&p);
}
// Show an error dialog box in a given window
// Return false to simplify code
static bool showError(Window* wnd, const char* text)
{
static const String name = "error_dialog";
if (!Client::valid())
return false;
NamedList p("");
p.addParam("text",text);
Client::self()->createDialog(YSTRING("message"),wnd,String::empty(),"error_dialog",&p);
return false;
}
// Show an error dialog box in a given window
static inline bool showAccDupError(Window* wnd)
{
return showError(wnd,"Another account with the same protocol, username and host already exists!");
}
// Show select account error dialog box in a given window
static inline bool showAccSelect(Window* wnd)
{
return showError(wnd,"You must choose an account");
}
// Show a duplicate room error dialog box in a given window
static inline bool showRoomDupError(Window* wnd)
{
return showError(wnd,"A chat room with the same username and server already exist!");
}
// Check text changes for user@domain
// Username changes: find '@', set domain if found
static bool checkUriTextChanged(Window* w, const String& sender, const String& text,
const String& usrName, const String& dName = String::empty())
{
if (sender != usrName)
return false;
int pos = text.find('@');
if (pos >= 0) {
NamedList p("");
p.addParam(usrName,text.substr(0,pos));
if (dName) {
String d = text.substr(pos + 1);
if (d) {
String tmp;
if (Client::self()->getText(dName,tmp,false,w) && !tmp) {
p.addParam(dName,d);
p.addParam("focus:" + dName,String::boolText(false));
}
}
}
Client::self()->setParams(&p,w);
}
return true;
}
// Check a room chat at groupchat.google.com
// Show an error if invalid
static bool checkGoogleRoom(const String& contact, Window* w = 0)
{
String room;
String domain;
splitContact(contact,room,domain);
if (!isGoogleMucDomain(domain))
return true;
if (room.startSkip("private-chat-",false) && Client::s_guidRegexp.matches(room))
return true;
String text;
text << "Invalid room '" << contact << "' for this domain!";
text << "\r\nThe format must be private-chat-8*HEX-4*HEX-4*HEX-4*HEX-12*HEX";
text << "\r\nE.g. private-chat-1a34561f-2d34-1111-dF23-29adc0347418";
if (w)
showError(w,text);
else
Client::openMessage(text);
return false;
}
// Check an URI read from UI
// Show an error if invalid
static bool checkUri(Window* w, const String& user, const String& domain, bool muc)
{
String text;
if (user) {
if (user.find('@') < 0) {
if (domain) {
if (domain.find('@') >= 0)
text << "Invalid domain";
}
else
text << "Domain can't be empty";
}
else
text << "Invalid " << (muc ? "room id" : "username");
}
else
text << (muc ? "Room id" : "Username") << " can't be empty";
if (text) {
showError(w,text);
return false;
}
if (!muc)
return true;
return checkGoogleRoom(user + "@" + domain,w);
}
// Retrieve resource status image with path
static inline String resStatusImage(int stat)
{
const char* img = lookup(stat,s_statusImage);
if (img)
return Client::s_skinPath + img;
return String();
}
// Retrieve the status of a contact
static inline int contactStatus(ClientContact& c)
{
ClientResource* res = c.status();
if (res)
return res->m_status;
return c.online() ? ClientResource::Online : ClientResource::Offline;
}
// Select a single item in a list containing exactly 1 item not
// matching s_notSelected
// Select the last item otherwise if selLast is true
static bool selectListItem(const String& name, Window* w, bool selLast = true,
bool selNotSelected = true)
{
NamedList p("");
Client::self()->getOptions(name,&p,w);
NamedString* sel = 0;
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!ns || Client::s_notSelected.matches(ns->name()))
continue;
if (!sel || selLast)
sel = ns;
else {
sel = 0;
break;
}
}
if (sel)
return Client::self()->setSelect(name,sel->name(),w);
return selNotSelected && Client::self()->setSelect(name,s_notSelected,w);
}
// Build a parameter list used to update an item in notification area
static inline void buildNotifAreaId(String& id, const char* itemType, const String& account,
const String& contact = String::empty())
{
id = itemType;
ClientContact::buildContactId(id,account,contact);
}
// Build a parameter list used to update an item in notification area
static NamedList* buildNotifArea(NamedList& list, const char* itemType, const String& account,
const String& contact, const char* title = 0, const char* extraParams = 0)
{
String id;
buildNotifAreaId(id,itemType,account,contact);
NamedList* upd = new NamedList(id);
list.addParam(new NamedPointer(id,upd,String::boolText(true)));
upd->addParam("item_type",itemType);
upd->addParam("account",account);
upd->addParam("contact",contact,false);
upd->addParam("title",title,false);
String params("item_type,account,contact,title");
params.append(extraParams,",");
upd->addParam("_yate_itemparams",params);
return upd;
}
// Show/hide a button in generic notification. Set its title also
static inline void setGenericNotif(NamedList& list, int index, const char* title)
{
String name;
name << "messages_" << index;
list.addParam("show:" + name,String::boolText(!TelEngine::null(title)));
list.addParam(name,title);
}
// Customize buttons in generic notification
static void setGenericNotif(NamedList& list, const char* title1 = 0,
const char* title2 = 0, const char* title3 = 0)
{
setGenericNotif(list,1,title1);
setGenericNotif(list,2,title2);
setGenericNotif(list,3,title3);
}
// Remove a notification area account/contact item
static inline void removeNotifArea(const char* itemType, const String& account,
const String& contact = String::empty(), Window* wnd = 0)
{
String id;
buildNotifAreaId(id,itemType,account,contact);
Client::self()->delTableRow(YSTRING("messages"),id,wnd);
}
// Remove all notifications belonging to an account
static void removeAccNotifications(ClientAccount* acc)
{
if (!acc)
return;
const String& account = acc->toString();
removeNotifArea("loginfail",account);
removeNotifArea("rosterreqfail",account);
}
// Build and add data used to update a channel item (conference/transfer)
static void channelItemAddUpdate(bool upd, NamedList& dest, const String& masterChan,
bool conf, bool start, const String& slaveId = String::empty(),
bool updateExisting = true)
{
String id;
if (!start)
id = slaveId;
else if (conf)
id = "conf_add_id";
else
id = "transfer_start_id";
if (!upd) {
dest.addParam(id,"");
return;
}
NamedList* item = new NamedList("");
if (conf) {
if (start) {
item->addParam("item_type","conf_add");
item->addParam("property:target:_yate_identity","conf_add_target:" + masterChan);
item->addParam("property:conf_add:_yate_identity","conf_add:" + masterChan);
}
else {
item->addParam("item_type","conf_item");
if (masterChan == slaveId)
item->addParam("property:conf_cancel:_yate_identity","calldroppeer:" + masterChan);
}
}
else if (start) {
item->addParam("item_type","transfer_start");
item->addParam("property:target:_yate_identity","transfer_start_target:" + masterChan);
item->addParam("property:transfer_start:_yate_identity","transfer_start:" + masterChan);
}
else
item->addParam("item_type","transfer_item");
if (start) {
item->addParam("cleartable:target","");
NamedList* callto = new NamedList("");
Client::self()->getOptions("callto",callto);
item->addParam(new NamedPointer("addlines:target",callto));
item->addParam("target","");
}
else {
ClientChannel* ch = ClientDriver::findChan(slaveId);
if (ch)
item->addParam("target",ch->partyName());
TelEngine::destruct(ch);
}
dest.addParam(new NamedPointer(id,item,String::boolText(updateExisting)));
}
// Build and add data used to update/delete a channel item (conference/transfer)
// Add update parameter to destination
static void channelItemBuildUpdate(bool upd, NamedList& dest, const String& masterChan,
bool conf, bool start, const String& slaveId = String::empty(),
bool updateExisting = true)
{
NamedList* tmp = new NamedList("");
channelItemAddUpdate(upd,*tmp,masterChan,conf,start,slaveId,updateExisting);
dest.addParam(new NamedPointer("updatetablerows:items",tmp));
}
// Build and add data used to reset target input (conference/transfer)
static void channelItemResetTarget(Window* wnd, const String& masterChan, bool conf)
{
NamedList p(s_channelList);
channelItemBuildUpdate(true,p,masterChan,conf,true,String::empty(),false);
Client::self()->setTableRow(s_channelList,masterChan,&p,wnd);
}
// Adjust channel item list height, buttons ...
// show: 1: handle show command, 0: handle hide command, -1: list changed
// itemAdded: handled if show is negative, true if item was added, false if item was removed
// Return the number of items
static int channelItemAdjustUiList(NamedList& dest, int show, bool itemAdded,
const String& chanId, bool conf)
{
static const int s_channelItemHeight = 26;
static const int s_channelMaxItems = 3;
static const int s_channelItemsMargin = 4;
static const int s_channelItemDataHeight = 18;
static const String s_getChkTrans = "getcheck:transfer";
static const String s_getChkConf = "getcheck:conference";
ClientChannel* chan = ClientDriver::findChan(chanId);
if (!chan)
return 0;
bool chanConf = chan->conference();
bool chanTrans = !chanConf && !chan->transferId().null();
bool hasPeer = chan->hasReconnPeer();
unsigned int slaves = chan->slavesCount();
TelEngine::destruct(chan);
bool activeShowConf = true;
bool activeShowTrans = true;
bool showItemsList = true;
bool clearItems = false;
int delta = 0;
int items = 0;
if (show >= 0) {
if (show > 0) {
if (conf) {
activeShowTrans = false;
// Add 2 to items: the master and conf add item
if (slaves) {
items = slaves + (hasPeer ? 2 : 1);
delta = s_channelItemDataHeight;
}
else
items = 1;
if (!chanConf)
clearItems = true;
}
else {
activeShowConf = false;
items = 1;
if (!chanTrans)
clearItems = true;
}
}
else {
showItemsList = false;
if (chanConf) {
activeShowTrans = false;
delta = s_channelItemDataHeight;
}
else if (chanTrans)
activeShowConf = false;
}
}
else {
NamedList p("");
p.addParam(s_getChkTrans,"");
p.addParam(s_getChkConf,"");
Client::self()->getTableRow(s_channelList,chanId,&p);
if (conf) {
showItemsList = p.getBoolValue(s_getChkConf);
if (showItemsList) {
activeShowTrans = false;
// Add 2 to items: the master and conf add item
if (slaves) {
items = slaves + (hasPeer ? 2 : 1);
delta = s_channelItemDataHeight;
}
else
items = 1;
bool on = (!itemAdded && !slaves);
if (on || (itemAdded && slaves == 1)) {
const char* s = String::boolText(on);
dest.addParam("show:direction",s);
dest.addParam("show:party",s);
dest.addParam("height:frame_call_data",s);
}
}
else {
if (slaves)
delta = s_channelItemDataHeight;
else {
// Item removed and no more slaves: restore normal channel
dest.addParam("show:direction",String::boolText(true));
dest.addParam("show:party",String::boolText(true));
dest.addParam("height:frame_call_data",String::boolText(true));
}
}
}
else {
showItemsList = p.getBoolValue(s_getChkTrans);
if (showItemsList) {
items = 1;
activeShowConf = false;
}
}
}
XDebug(ClientDriver::self(),DebugAll,
"channelItemAdjustUiList(%d,%u,%s,%u) showItemsList=%u items=%d delta=%d",
show,itemAdded,chanId.c_str(),conf,showItemsList,items,delta);
dest.addParam("show:frame_items",String::boolText(showItemsList));
if (clearItems)
dest.addParam("cleartable:items","");
if (showItemsList) {
if (items) {
int h = (items <= s_channelMaxItems) ? items : s_channelMaxItems;
h *= s_channelItemHeight;
int frmH = h + s_channelItemsMargin;
dest.addParam("_yate_itemheight_delta",String(frmH - delta));
dest.addParam("height:frame_items",String(frmH));
dest.addParam("height:items",String(h));
}
}
else {
dest.addParam("_yate_itemheight_delta",String(-delta));
}
dest.addParam("active:transfer",String::boolText(activeShowTrans));
dest.addParam("active:conference",String::boolText(activeShowConf));
// Show transfer with call in transfer: return 0 to prevent showing
// transfer start item
if (show > 0 && !conf && slaves)
return 0;
return items;
}
// Request to the client to log a chat entry
static bool logChat(ClientContact* c, unsigned int time, bool send, bool delayed,
const String& body, bool roomChat = true, const String& nick = String::empty())
{
if (!c)
return false;
if (s_chatLog != ChatLogSaveAll && s_chatLog != ChatLogSaveUntilLogout)
return false;
if (!Client::self())
return false;
MucRoom* room = c->mucRoom();
NamedList p("");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
if (!room) {
p.addParam("contactname",c->m_name);
p.addParam("sender",send ? "" : c->m_name.c_str());
}
else {
p.addParam("muc",String::boolText(true));
p.addParam("roomchat",String::boolText(roomChat));
p.addParam("contactname",roomChat ? room->resource().m_name : nick);
p.addParam("sender",send ? "" : nick.c_str());
}
p.addParam("time",String(time));
p.addParam("send",String::boolText(send));
if (!send && delayed)
p.addParam("delayed",String::boolText(true));
p.addParam("text",body);
return Client::self()->action(0,YSTRING("archive:logchat"),&p);
}
// Show contact archive log
static bool logShow(ClientContact* c, bool roomChat = true,
const String& nick = String::empty())
{
if (!(c && Client::self()))
return false;
MucRoom* room = c->mucRoom();
NamedList p("");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
if (room) {
p.addParam("muc",String::boolText(true));
p.addParam("roomchat",String::boolText(roomChat));
p.addParam("contactname",nick,false);
}
return Client::self()->action(0,YSTRING("archive:showchat"),&p);
}
// Close archive session
static bool logCloseSession(ClientContact* c, bool roomChat = true,
const String& nick = String::empty())
{
if (!(c && Client::self()))
return false;
MucRoom* room = c->mucRoom();
NamedList p("");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
if (room) {
p.addParam("muc",String::boolText(true));
p.addParam("roomchat",String::boolText(roomChat));
p.addParam("contactname",nick,false);
}
return Client::self()->action(0,YSTRING("archive:closechatsession"),&p);
}
// Clear an account's log
static bool logClearAccount(const String& account)
{
if (!Client::self())
return false;
NamedList p("");
p.addParam("account",account);
return Client::self()->action(0,YSTRING("archive:clearaccountnow"),&p);
}
// Close all MUC log sessions of a room
static void logCloseMucSessions(MucRoom* room)
{
if (!room)
return;
Window* w = room->getChatWnd();
if (w) {
NamedList p("");
Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,w);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!(ns && ns->name()))
continue;
MucRoomMember* m = room->findMemberById(ns->name());
if (m)
logCloseSession(room,false,m->m_name);
}
}
else {
for (ObjList* o = room->resources().skipNull(); o; o = o->skipNext()) {
MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
logCloseSession(room,false,m->m_name);
}
}
logCloseSession(room);
}
// Update protocol related page(s) in account edit/add or wizard
static void selectProtocolSpec(NamedList& p, const String& proto, bool advanced,
const String& protoList)
{
p.setParam("select:" + protoList,proto);
p.setParam("select:acc_proto_cfg","acc_proto_cfg_" + getProtoPage(proto));
p.setParam("select:acc_proto_advanced",
"acc_proto_advanced_" + getProtoPage(advanced ? proto : String::empty()));
}
// Update protocol specific data
// Set protocol specific widgets: options, address, port ....
// Text widgets' name should start with acc_proto_protocolpagename_
// Option widgets' name should start with acc_proto_protocolpagename_opt_
static void updateProtocolSpec(NamedList& p, const String& proto, bool edit,
const NamedList& params = NamedList::empty())
{
DDebug(ClientDriver::self(),DebugAll,"updateProtocolSpec(%s,%u,%s)",
proto.c_str(),edit,params.c_str());
// Account common params
String prefix = "acc_";
for (const String* par = s_accParams; !par->null(); par++)
p.setParam(prefix + *par,params.getValue(*par));
// Protocol specific params
prefix << "proto_" << getProtoPage(proto) << "_";
for (const String* par = s_accProtoParams; !par->null(); par++)
p.setParam(prefix + *par,params.getValue(*par));
NamedIterator iter(s_accProtoParamsSel);
for (const NamedString* ns = 0; 0 != (ns = iter.get());)
p.setParam(prefix + ns->name(),params.getValue(ns->name(),*ns));
// Set default resource for new accounts if not already set
if (!edit) {
if (proto == s_jabber) {
String tmp = prefix + "resource";
if (!p.getValue(tmp))
p.setParam(tmp,Engine::config().getValue("client","resource","Yate"));
}
else if (proto == s_h323) {
String tmp = prefix + "authmethods";
if (!p.getValue(tmp))
p.setParam(tmp,Engine::config().getValue("client","authmethods","MD5"));
}
}
// Options
prefix << "opt_";
ObjList* opts = params["options"].split(',',false);
for (ObjList* o = ClientLogic::s_accOptions.skipNull(); o; o = o->skipNext()) {
String* opt = static_cast<String*>(o->get());
bool checked = (opts && 0 != opts->find(*opt));
p.setParam("check:" + prefix + *opt,String::boolText(checked));
}
TelEngine::destruct(opts);
dumpList(p,"updateProtocolSpec");
}
// Handle protocol/providers select for DefaultLogic in account edit/add or wizard
static bool handleProtoProvSelect(Window* w, const String& name, const String& item)
{
// Flag used to avoid resetting the providers list in provider change handler
static bool s_changing = false;
// Handle protocol selection in edit or wizard window:
// Show/hide protocol specific options
// Select nothing in providers
bool noWiz = (name == s_accProtocol);
if (noWiz || name == s_accWizProtocol) {
if (!Client::valid())
return false;
bool adv = false;
Client::self()->getCheck(YSTRING("acc_showadvanced"),adv,w);
NamedList p("");
selectProtocolSpec(p,item,adv,name);
// Reset provider if not changing due to provider change
if (!s_changing)
p.setParam("select:" + (noWiz ? s_accProviders : s_accWizProviders),s_notSelected);
dumpList(p,"Handle protocol select",w);
Client::self()->setParams(&p,w);
return true;
}
// Apply provider template
noWiz = (name == s_accProviders);
if (!noWiz && name != s_accWizProviders)
return false;
if (Client::s_notSelected.matches(item))
return true;
if (!Client::valid())
return true;
// Get data and update UI
NamedList* sect = Client::s_providers.getSection(item);
if (!sect)
return true;
NamedList p("");
const String& proto = (*sect)[YSTRING("protocol")];
bool adv = false;
Client::self()->getCheck(YSTRING("acc_showadvanced"),adv,w);
selectProtocolSpec(p,proto,adv,noWiz ? s_accProtocol : s_accWizProtocol);
updateProtocolSpec(p,proto,w && w->context(),*sect);
dumpList(p,"Handle provider select",w);
// Avoid resetting protocol while applying provider
s_changing = true;
Client::self()->setParams(&p,w);
s_changing = false;
return true;
}
// Update the protocol list from global
// filterTypeTel: Optional pointer to protocol telephony/IM type
// specParams: Optional pointer to parameters list used to update protocol specs
// firstProto: Optional pointer to String to be filled with the first protocol in the list
static void updateProtocolList(Window* w, const String& list, bool* filterTypeTel = 0,
NamedList* specParams = 0, String* firstProto = 0)
{
DDebug(ClientDriver::self(),DebugAll,"updateProtocolList(%p,%s,%p,%p,%p)",
w,list.c_str(),filterTypeTel,specParams,firstProto);
ObjList tmp;
ClientLogic::s_protocolsMutex.lock();
for (ObjList* o = ClientLogic::s_protocols.skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
if (TelEngine::null(s))
continue;
if (!filterTypeTel || *filterTypeTel == isTelProto(*s))
tmp.append(new String(*s));
}
ClientLogic::s_protocolsMutex.unlock();
for (ObjList* o = tmp.skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
if (TelEngine::null(s))
continue;
bool ok = list.null() || Client::self()->updateTableRow(list,*s,0,false,w);
if (ok && firstProto && firstProto->null())
*firstProto = *s;
if (specParams)
updateProtocolSpec(*specParams,*s,false);
}
}
// Update a provider item in a given list
// filterTypeTel: Optional pointer to protocol telephony/IM type
static bool updateProvidersItem(Window* w, const String& list, const NamedList& prov,
bool* filterTypeTel = 0)
{
if (!Client::valid())
return false;
const String& proto = prov[YSTRING("protocol")];
if (proto && (!filterTypeTel || *filterTypeTel == isTelProto(proto)))
return Client::self()->updateTableRow(list,prov,0,false,w);
return false;
}
// Utility function used to build channel status
static void buildStatus(String& status, const char* stat, const char* addr,
const char* id, const char* reason = 0)
{
status << stat;
if (addr || id)
status << ": " << (addr ? addr : id);
if (reason)
status << " reason: " << reason;
}
// Check if a given parameter is present in a list.
// Update it from UI if not present or empty
static bool checkParam(NamedList& p, const String& param, const String& widget,
bool checkNotSel, Window* wnd = 0)
{
NamedString* tmp = p.getParam(param);
if (tmp && *tmp)
return true;
if (!Client::self())
return false;
String value;
Client::self()->getText(widget,value,false,wnd);
value.trimBlanks();
bool ok = value && !(checkNotSel && Client::s_notSelected.matches(value));
if (ok)
p.setParam(param,value);
return ok;
}
// Utility: activate the calls page
static void activatePageCalls(Window* wnd = 0, bool selTab = true)
{
if (!Client::valid())
return;
NamedList p("");
p.addParam("check:ctrlCalls",String::boolText(true));
p.addParam("select:framePages","PageCalls");
if (selTab)
p.addParam("select:" + s_mainwindowTabs,"tabTelephony");
Client::self()->setParams(&p,wnd);
}
// Check if the calls page is active
static bool isPageCallsActive(Window* wnd, bool checkTab)
{
if (!Client::valid())
return false;
String sel;
if (checkTab) {
Client::self()->getSelect(s_mainwindowTabs,sel,wnd);
if (sel != YSTRING("tabTelephony"))
return false;
sel.clear();
}
Client::self()->getSelect(YSTRING("framePages"),sel,wnd);
return sel == YSTRING("PageCalls");
}
// Retrieve a contact edit/info window.
// Create it if requested and not found.
// Set failExists to true to return 0 if already exists
static Window* getContactInfoEditWnd(bool edit, bool room, ClientContact* c,
bool create = false, bool failExists = false)
{
if (!Client::valid())
return 0;
const char* wnd = 0;
if (edit) {
if (c && c->mucRoom())
room = true;
wnd = !room ? "contactedit" : "chatroomedit";
}
else
wnd = "contactinfo";
String wname(wnd);
wname << "_" << (c ? c->toString().c_str() : String((unsigned int)Time::msecNow()).c_str());
Window* w = Client::self()->getWindow(wname);
if (w)
return failExists ? 0 : w;
if (!create)
return 0;
Client::self()->createWindowSafe(wnd,wname);
w = Client::self()->getWindow(wname);
if (w && c) {
NamedList p("");
p.addParam("context",c->toString());
if (!edit)
p.addParam("property:" + s_chatEdit + ":_yate_identity",
s_chatEdit + ":" + c->toString());
Client::self()->setParams(&p,w);
}
return w;
}
// Retrieve a contact share(d) files window.
// Create it if requested and not found.
// Set failExists to true to return 0 if already exists
static Window* getContactShareWnd(bool share, ClientContact* c, bool create = false,
bool failExists = false)
{
if (!Client::valid())
return 0;
if (!c)
return 0;
const char* wnd = share ? "contactfs" : "contactfsd";
String wname(wnd);
wname << "_" << c->toString().c_str();
Window* w = Client::self()->getWindow(wname);
if (w)
return failExists ? 0 : w;
if (!create)
return 0;
Client::self()->createWindowSafe(wnd,wname);
return Client::self()->getWindow(wname);
}
// Build a share item to be added in UI
static NamedList* buildShareItemUI(const NamedString* ns)
{
if (!ns)
return 0;
NamedList* p = new NamedList(ns->name());
addLastItem(*p,"name",*ns,ns->name());
p->addParam("path",ns->name());
return p;
}
// Build a share item to be added in UI
static NamedList* buildShareItemUI(ClientContact* c, const String& item)
{
if (c && item)
return buildShareItemUI(c->share().getParam(item));
return 0;
}
// Retrieve a contact share files window.
// Create it if requested and not found.
// Set failExists to true to return 0 if already exists
static void updateContactShareWnd(Window* w, ClientContact* c, bool cData = true, bool share = true)
{
if (!(c && (cData || share) && Client::valid()))
return;
if (!w) {
w = getContactShareWnd(true,c);
if (!w)
return;
}
if (cData) {
NamedList p("");
p.addParam("context",c->toString());
String tmp;
tmp << "Share files with " << c->m_name;
if (c->uri())
tmp << " [" << c->uri() << "]";
p.addParam("title",tmp);
p.addParam("username",c->uri());
p.addParam("account",c->accountName());
Client::self()->setParams(&p,w);
}
if (share) {
Client::self()->clearTable(s_fileShareList,w);
if (c->haveShare()) {
NamedList tmp("");
NamedIterator iter(c->share());
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
NamedList* p = buildShareItemUI(ns);
if (p)
tmp.addParam(new NamedPointer(ns->name(),p,String::boolText(true)));
}
Client::self()->updateTableRows(s_fileShareList,&tmp,false,w);
}
}
}
// Retrieve a contact shared files window.
// Create it if requested and not found.
// Set failExists to true to return 0 if already exists
static void updateContactSharedWnd(Window* w, ClientContact* c, bool cData = true, bool shared = true)
{
if (!(c && (cData || shared) && Client::valid()))
return;
if (!w) {
w = getContactShareWnd(false,c);
if (!w)
return;
}
if (cData) {
NamedList p("");
p.addParam("context",c->toString());
String tmp;
tmp << "Files shared by " << c->m_name;
if (c->uri())
tmp << " [" << c->uri() << "]";
p.addParam("title",tmp);
p.addParam("username",c->uri());
p.addParam("account",c->accountName());
Client::self()->setParams(&p,w);
}
if (shared) {
Client::self()->clearTable(s_fileSharedDirsList,w);
Client::self()->clearTable(s_fileSharedDirsContent,w);
NamedList tmp("");
for (ObjList* o = c->shared().skipNull(); o; o = o->skipNext()) {
ClientDir* dir = static_cast<ClientDir*>(o->get());
sharedDirsAddUpdate(tmp,c,dir,String::empty(),0,true);
}
Client::self()->updateTableRows(s_fileSharedDirsList,&tmp,false,w);
}
}
// Display the window showing the files we share to a contact
static bool showContactShareWnd(ClientContact* c)
{
Window* w = getContactShareWnd(true,c,true);
if (!w)
return false;
updateContactShareWnd(w,c);
return Client::self()->setVisible(w->toString(),true,true);
}
// Display the window showing the files we share to a contact
static bool showContactSharedWnd(ClientContact* c)
{
Window* w = getContactShareWnd(false,c,true);
if (!w)
return false;
updateContactSharedWnd(w,c);
return Client::self()->setVisible(w->toString(),true,true);
}
// Clear shared items from UI. Clear table if dir is 0
static void removeSharedFromUI(ClientContact* c, ClientDir* dir)
{
if (!(c && dir))
return;
Window* w = getContactShareWnd(false,c);
if (!w)
return;
if (!dir) {
Client::self()->clearTable(s_fileSharedDirsList,w);
return;
}
NamedList upd("");
for (ObjList* o = dir->children().skipNull(); o; o = o->skipNext()) {
ClientDir* d = static_cast<ClientDir*>(o->get());
String s;
sharedBuildId(s,*dir,String::empty(),d->name());
upd.addParam(s,"");
}
Client::self()->updateTableRows(s_fileSharedDirsList,&upd,false,w);
}
// Update account list in chat account add windows
// Select single updated account
static void updateChatAccountList(const String& account, bool upd)
{
if (!(Client::valid() && account))
return;
ObjList* list = Client::listWindows();
for (ObjList* o = (list ? list->skipNull() : 0); o; o = o->skipNext()) {
String* id = static_cast<String*>(o->get());
bool isContact = id->startsWith("contactedit_");
if (!(isContact || id->startsWith("chatroomedit_")))
continue;
Window* w = Client::self()->getWindow(*id);
if (!w || (isContact && w->context()))
continue;
if (upd) {
Client::self()->updateTableRow(s_chatAccount,account,0,false,w);
selectListItem(s_chatAccount,w,false,false);
}
else {
// Avoid showing another selected account
String tmp;
Client::self()->getSelect(s_chatAccount,tmp,w);
if (tmp && tmp == account)
Client::self()->setSelect(s_chatAccount,s_notSelected,w);
Client::self()->delTableRow(s_chatAccount,account,w);
}
}
TelEngine::destruct(list);
}
// Retrieve an account's enter password window
// Create it if requested and not found.
static Window* getAccPasswordWnd(const String& account, bool create)
{
if (!(Client::valid() && account))
return 0;
String wname(account + "EnterPassword");
Window* w = Client::self()->getWindow(wname);
if (!create)
return w;
if (!w) {
Client::self()->createWindowSafe(YSTRING("inputpwd"),wname);
w = Client::self()->getWindow(wname);
if (!w) {
Debug(ClientDriver::self(),DebugNote,"Failed to build account password window!");
return 0;
}
}
NamedList p("");
String text;
text << "Enter password for account '" << account << "'";
p.addParam("inputpwd_text",text);
p.addParam("inputpwd_password","");
p.addParam("check:inputpwd_savepassword",String::boolText(false));
p.addParam("context","loginpassword:" + account);
Client::self()->setParams(&p,w);
Client::self()->setVisible(wname,true,true);
return w;
}
// Close an account's password window
static void closeAccPasswordWnd(const String& account)
{
Window* w = getAccPasswordWnd(account,false);
if (w)
Client::self()->closeWindow(w->toString());
}
// Retrieve an account's enter credentials window
// Create it if requested and not found.
static Window* getAccCredentialsWnd(const NamedList& account, bool create,
const String& text = String::empty())
{
if (!(Client::valid() && account))
return 0;
String wname(account + "EnterCredentials");
Window* w = Client::self()->getWindow(wname);
if (!create)
return w;
if (!w) {
Client::self()->createWindowSafe(YSTRING("inputacccred"),wname);
w = Client::self()->getWindow(wname);
if (!w) {
Debug(ClientDriver::self(),DebugNote,"Failed to build account credentials window!");
return 0;
}
}
NamedList p("");
p.addParam("inputacccred_text",text);
p.addParam("inputacccred_username",account.getValue(YSTRING("username")));
p.addParam("inputacccred_password",account.getValue(YSTRING("password")));
p.addParam("check:inputacccred_savepassword",
String(account.getBoolValue(YSTRING("savepassword"))));
p.addParam("context","logincredentials:" + account);
Client::self()->setParams(&p,w);
Client::self()->setVisible(wname,true,true);
return w;
}
// Close an account's enter credentials window
static void closeAccCredentialsWnd(const String& account)
{
NamedList tmp(account);
Window* w = getAccCredentialsWnd(tmp,false);
if (w)
Client::self()->closeWindow(w->toString());
}
// Build a chat history item parameter list
static NamedList* buildChatParams(const char* text, const char* sender,
unsigned int sec, bool delay = false, const char* delaysource = 0)
{
NamedList* p = new NamedList("");
p->addParam("text",text);
p->addParam("sender",sender,false);
String ts;
String dl;
if (!delay)
Client::self()->formatDateTime(ts,sec,"hh:mm:ss",false);
else {
Client::self()->formatDateTime(ts,sec,"dd.MM.yyyy hh:mm:ss",false);
if (!TelEngine::null(delaysource))
dl << "\r\nDelayed by: " << delaysource;
}
p->addParam("time",ts,false);
p->addParam("delayed_by",dl,false);
return p;
}
// Build a chat state history item parameter list
static bool buildChatState(String& buf, const NamedList& params, const char* sender)
{
const String& state = params[YSTRING("chatstate")];
if (!state)
return false;
buf = s_chatStates[state];
if (!buf)
return true;
NamedList tmp("");
tmp.addParam("sender",sender);
tmp.addParam("state",state);
tmp.replaceParams(buf);
return true;
}
// Add a notification text in contact's chat history
static void addChatNotify(ClientContact& c, const char* text,
unsigned int sec = Time::secNow(), const char* what = "notify",
const String& roomId = String::empty())
{
if (!c.hasChat())
return;
NamedList* p = buildChatParams(text,0,sec);
MucRoom* room = c.mucRoom();
if (!room)
c.addChatHistory(what,p);
else
room->addChatHistory(roomId ? roomId : room->resource().toString(),what,p);
}
// Add an online/offline notification text in contact's chat history
static inline void addChatNotify(ClientContact& c, bool online,
bool account = false, unsigned int sec = Time::secNow())
{
String text;
if (!account)
text << c.m_name;
else
text = "Account";
text << " is " << (online ? "online" : "offline");
addChatNotify(c,text,sec);
}
// Add/Update a contact list item
static void updateContactList(ClientContact& c, const String& inst = String::empty(),
const char* uri = 0)
{
DDebug(ClientDriver::self(),DebugAll,"updateContactList(%s,%s,%s)",
c.toString().c_str(),inst.c_str(),uri);
NamedList p("");
p.addParam("name",c.m_name);
p.addParam("number/uri",TelEngine::null(uri) ? c.uri().c_str() : uri);
String id;
c.buildInstanceId(id,inst);
Client::self()->updateTableRow(s_contactList,id,&p);
}
// Remove all contacts starting with a given string
static void removeContacts(const String& idstart)
{
NamedList p("");
if (!Client::self()->getOptions(s_contactList,&p))
return;
DDebug(ClientDriver::self(),DebugAll,"removeContacts(%s)",idstart.c_str());
unsigned int n = p.count();
for (unsigned int i = 0; i < n; i++) {
NamedString* param = p.getParam(i);
if (param && param->name().startsWith(idstart,false))
Client::self()->delTableRow(s_contactList,param->name());
}
}
// Contact deleted: clear UI
static void contactDeleted(ClientContact& c)
{
DDebug(ClientDriver::self(),DebugAll,"contactDeleted(%s)",c.toString().c_str());
// Add chat notification and update status
if (c.hasChat() && c.online()) {
addChatNotify(c,false);
NamedList p("");
String img = resStatusImage(ClientResource::Offline);
p.addParam("image:status_image",img);
p.addParam("status_text",ClientResource::statusDisplayText(ClientResource::Offline));
c.updateChatWindow(p,0,img);
}
// Remove from chat
Client::self()->delTableRow(s_chatContactList,c.toString());
// Remove instances from contacts list
String instid;
removeContacts(c.buildInstanceId(instid));
// Close contact file share(d) window
Window* w = getContactShareWnd(true,&c,false,false);
if (w)
Client::self()->closeWindow(w->toString(),false);
w = getContactShareWnd(false,&c,false,false);
if (w)
Client::self()->closeWindow(w->toString(),false);
// Close chat session
logCloseSession(&c);
}
// Remove all account contacts from UI
static void clearAccountContacts(ClientAccount& a)
{
DDebug(ClientDriver::self(),DebugAll,"clearAccountContacts(%s)",a.toString().c_str());
ObjList* o = 0;
while (0 != (o = a.contacts().skipNull())) {
ClientContact* c = static_cast<ClientContact*>(o->get());
contactDeleted(*c);
a.removeContact(c->toString());
}
// Clear account own instances
if (a.contact() && a.contact()->resources().skipNull()) {
String instid;
a.contact()->buildInstanceId(instid);
a.contact()->resources().clear();
removeContacts(instid);
}
}
// Set account own contact
static void setAccountContact(ClientAccount* acc)
{
if (!acc)
return;
URI tmp(acc->toString());
String uri(tmp.getUser() + "@" + tmp.getHost());
String cId;
ClientContact::buildContactId(cId,acc->toString(),uri);
acc->setContact(new ClientContact(0,cId,acc->toString(),uri));
}
// Retrieve the selected account
static ClientAccount* selectedAccount(ClientAccountList& accounts, Window* wnd = 0,
const String& list = String::empty())
{
String account;
if (!Client::valid())
return 0;
if (!list)
Client::self()->getSelect(s_accountList,account,wnd);
else
Client::self()->getSelect(list,account,wnd);
return account ? accounts.findAccount(account) : 0;
}
// Retrieve the chat contact
static ClientContact* selectedChatContact(ClientAccountList& accounts,
Window* wnd = 0, bool rooms = true)
{
String c;
if (Client::valid())
Client::self()->getSelect(s_chatContactList,c,wnd);
if (!c)
return 0;
return rooms ? accounts.findAnyContact(c) : accounts.findContact(c);
}
// Fill acc_login/logout active parameters
static void fillAccLoginActive(NamedList& p, ClientAccount* acc)
{
if (acc && isTelProto(acc->protocol())) {
p.addParam("active:" + s_actionLogin,String::boolText(true));
p.addParam("active:" + s_actionLogout,String::boolText(true));
}
else {
bool offline = !acc || acc->resource().offline();
p.addParam("active:" + s_actionLogin,String::boolText(acc && offline));
p.addParam("active:" + s_actionLogout,String::boolText(!offline));
}
}
// Fill acc_del/edit active parameters
static inline void fillAccEditActive(NamedList& p, bool active)
{
const char* tmp = String::boolText(active);
p.addParam("active:acc_del",tmp);
p.addParam("active:acc_edit",tmp);
}
// Utility function used to save a widget's text
static inline void saveParam(NamedList& params,
const String& prefix, const String& param, Window* wnd)
{
String val;
Client::self()->getText(prefix + param,val,false,wnd);
params.setParam(param,val);
}
// Utility function used to save a widget's check state
static inline void saveCheckParam(NamedList& params, const String& prefix,
const String& param, Window* wnd, bool defVal = false)
{
Client::self()->getCheck(prefix + param,defVal,wnd);
params.setParam(param,String::boolText(defVal));
}
// Retrieve account protocol, username, host from UI
static bool getAccount(Window* w, String* proto, String* user, String* host)
{
if (!(proto || user || host))
return false;
bool noWiz = !s_accWizard->isWindow(w);
String p;
if (host && !proto)
proto = &p;
if (proto) {
Client::self()->getText(noWiz ? s_accProtocol : s_accWizProtocol,
*proto,false,w);
if (proto->null()) {
showError(w,"A protocol must be selected");
return false;
}
}
if (user) {
Client::self()->getText("acc_username",*user,false,w);
if (user->null()) {
showError(w,"Account username is mandatory");
return false;
}
}
if (host) {
String prefix;
prefix << "acc_proto_" << getProtoPage(*proto) << "_";
Client::self()->getText(prefix + "domain",*host,false,w);
if (host->null()) {
if (*proto == s_jabber) {
showError(w,"Account domain is mandatory for the selected protocol");
return false;
}
Client::self()->getText(prefix + "server",*host,false,w);
if (host->null()) {
showError(w,"You must enter a domain or server");
return false;
}
}
}
return true;
}
// Read room data from a window
// Check the room pointer on return: 0 means failure
// Return true if connection data changed (username, server, password)
static bool getRoom(Window* w, ClientAccount* acc, bool permanent,
bool denyExist, MucRoom*& r, bool& dataChanged, bool hasRoomSrv = true)
{
r = 0;
if (!w)
return false;
if (!acc) {
showError(w,"No account selected");
return false;
}
if (!acc->resource().online()) {
showError(w,"The account is offline");
return false;
}
String contact;
String room;
String server;
if (hasRoomSrv) {
Client::self()->getText(YSTRING("room_room"),room,false,w);
Client::self()->getText(YSTRING("room_server"),server,false,w);
contact << room << "@" << server;
}
else {
Client::self()->getText(YSTRING("room_uri"),contact,false,w);
splitContact(contact,room,server);
}
if (!checkUri(w,room,server,true))
return false;
String id;
ClientContact::buildContactId(id,acc->toString(),contact);
r = acc->findRoom(id);
bool changed = (r == 0);
dataChanged = changed;
if (!r) {
// Check if a contact with the same user@domain already exists
if (permanent && acc->findContact(id)) {
showError(w,"A contact with the same username and domain already exist");
return false;
}
r = new MucRoom(acc,id,0,contact,0);
}
else if (denyExist && (r->local() || r->remote())) {
r = 0;
return showRoomDupError(w);
}
String nick;
String pwd;
String name;
Client::self()->getText(YSTRING("room_nick"),nick,false,w);
Client::self()->getText(YSTRING("room_password"),pwd,false,w);
// Join room wizard don't have name set, avoid resetting it
if (hasRoomSrv)
Client::self()->getText(YSTRING("room_name"),name,false,w);
else
name = r->m_name;
bool autoJoin = false;
Client::self()->getCheck(YSTRING("room_autojoin"),autoJoin,w);
bool hist = true;
Client::self()->getCheck(YSTRING("room_history"),hist,w);
String lastHist;
if (hist) {
bool t = false;
if (Client::self()->getCheck(YSTRING("room_historylast"),t,w) && t)
Client::self()->getText(YSTRING("room_historylast_value"),lastHist,false,w);
}
if (lastHist.toInteger() < 1)
lastHist.clear();
// Update room data. Check if connection related data changed
if (setChangedString(r->m_password,pwd))
changed = dataChanged = true;
dataChanged = setChangedString(r->m_name,name ? name : contact) || dataChanged;
dataChanged = setChangedParam(r->m_params,YSTRING("nick"),nick) || dataChanged;
dataChanged = setChangedParam(r->m_params,YSTRING("autojoin"),String::boolText(autoJoin)) ||
dataChanged;
dataChanged = setChangedParam(r->m_params,YSTRING("history"),String::boolText(hist)) ||
dataChanged;
dataChanged = setChangedParam(r->m_params,YSTRING("historylast"),lastHist) || dataChanged;
// Permanent room
if (permanent) {
if (!(r->local() && r->remote()))
dataChanged = true;
r->setLocal(true);
r->setRemote(true);
}
return changed;
}
// Fill a list used to update muc room edit/join window
static void fillRoomParams(NamedList& p, MucRoom* r = 0, bool hasRoomSrv = true)
{
bool autoJoin = false;
bool hist = true;
String last;
if (r) {
p.addParam("room_account",r->accountName());
if (hasRoomSrv) {
p.addParam("room_room",r->uri().getUser());
p.addParam("room_server",r->uri().getHost());
}
else
p.addParam("room_uri",r->uri());
p.addParam("room_nick",r->m_params[YSTRING("nick")]);
p.addParam("room_password",r->m_password);
p.addParam("room_name",r->m_name);
autoJoin = r->m_params.getBoolValue(YSTRING("autojoin"));
hist = r->m_params.getBoolValue(YSTRING("history"));
if (hist)
last = r->m_params[YSTRING("historylast")];
}
else {
p.addParam("room_account","");
if (hasRoomSrv) {
p.addParam("room_room","");
p.addParam("room_server","");
}
else
p.addParam("room_uri","");
p.addParam("room_nick","");
p.addParam("room_password","");
p.addParam("room_name","");
}
p.addParam("check:room_autojoin",String::boolText(autoJoin));
p.addParam("check:room_history",String::boolText(hist));
p.addParam("check:room_historylast",String::boolText(hist && last));
if (last.toInteger() <= 0)
last = "30";
p.addParam("room_historylast_value",last);
}
// Retrieve account data from UI
static bool getAccount(Window* w, NamedList& p, ClientAccountList& accounts)
{
if (!Client::valid())
return false;
String proto;
String user;
String host;
if (!getAccount(w,&proto,&user,&host))
return false;
String id;
p.assign(DefaultLogic::buildAccountId(id,proto,user,host));
p.addParam("enabled",String::boolText(true));
// Account flags
p.addParam("protocol",proto);
String prefix = "acc_";
// Save account parameters
for (const String* par = s_accParams; !par->null(); par++)
saveParam(p,prefix,*par,w);
for (const String* par = s_accBoolParams; !par->null(); par++)
saveCheckParam(p,prefix,*par,w);
prefix << "proto_" << getProtoPage(proto) << "_";
for (const String* par = s_accProtoParams; !par->null(); par++)
saveParam(p,prefix,*par,w);
NamedIterator iter(s_accProtoParamsSel);
for (const NamedString* ns = 0; 0 != (ns = iter.get());)
saveParam(p,prefix,ns->name(),w);
// Options
prefix << "opt_";
String options;
for (ObjList* o = ClientLogic::s_accOptions.skipNull(); o; o = o->skipNext()) {
String* opt = static_cast<String*>(o->get());
bool checked = false;
Client::self()->getCheck(prefix + *opt,checked,w);
if (checked)
options.append(*opt,",");
}
bool reg = false;
Client::self()->getCheck(YSTRING("acc_register"),reg,w);
if (reg)
options.append("register",",");
p.setParam("options",options);
dumpList(p,"Got account",w);
return true;
}
// Update account status and login/logout active status if selected
static void updateAccountStatus(ClientAccount* acc, ClientAccountList* accounts,
Window* wnd = 0)
{
if (!acc)
return;
NamedList p("");
acc->fillItemParams(p);
p.addParam("check:enabled",String::boolText(acc->startup()));
p.addParam("status_image",resStatusImage(acc->resource().m_status),false);
Client::self()->updateTableRow(s_accountList,acc->toString(),&p,false,wnd);
// Remove pending requests if offline
if (acc->resource().offline())
PendingRequest::clear(acc->toString());
// Set login/logout enabled status
bool selected = accounts && acc == selectedAccount(*accounts,wnd);
NamedList pp("");
if (selected)
fillAccLoginActive(pp,acc);
Client::self()->setParams(&pp,wnd);
}
// Add account pending status
static void addAccPendingStatus(NamedList& p, ClientAccount* acc, AccountStatus* stat = 0)
{
if (!(acc && acc->hasPresence()))
return;
if (!stat)
stat = AccountStatus::current();
if (!stat)
return;
const char* s = lookup(stat->status(),ClientResource::s_statusName);
acc->m_params.addParam("internal.status.status",s,false);
p.addParam("show",s,false);
acc->m_params.addParam("internal.status.text",stat->text(),false);
p.addParam("status",stat->text(),false);
}
// Set account status from global. Update UI. Notify remote party
// Use current status if none specified
static void setAccountStatus(ClientAccountList* accounts, ClientAccount* acc,
AccountStatus* stat = 0, NamedList* upd = 0, bool checkPwd = true)
{
if (!acc)
return;
if (!stat)
stat = AccountStatus::current();
if (!stat)
return;
Debug(ClientDriver::self(),DebugInfo,
"setAccountsStatus(%s) set=(%u,%s) acc=(%u,%s)",
acc->toString().c_str(),stat->status(),stat->text().c_str(),
acc->resource().m_status,acc->resource().m_text.c_str());
if (acc->resource().m_status == ClientResource::Connecting &&
stat->status() != ClientResource::Offline)
return;
bool changed = false;
bool login = false;
bool logout = false;
switch (stat->status()) {
case ClientResource::Online:
if (acc->resource().m_status == ClientResource::Offline)
changed = login = true;
else {
changed = acc->resource().setStatus(stat->status());
if (acc->hasPresence())
changed = acc->resource().setStatusText(stat->text()) || changed;
}
break;
case ClientResource::Offline:
changed = logout = !acc->resource().offline();
break;
case ClientResource::Busy:
case ClientResource::Dnd:
case ClientResource::Away:
case ClientResource::Xa:
if (!acc->hasPresence()) {
// Just connect the account if not already connected
changed = login = acc->resource().offline();
break;
}
if (!acc->resource().offline()) {
changed = acc->resource().setStatus(stat->status());
changed = acc->resource().setStatusText(stat->text()) || changed;
}
else
changed = login = true;
break;
}
if (!changed)
return;
acc->m_params.clearParam(YSTRING("internal.status"),'.');
Message* m = 0;
if (login || logout) {
if (login && checkPwd && !acc->params().getValue(YSTRING("password"))) {
getAccPasswordWnd(acc->toString(),true);
return;
}
m = userLogin(acc,login);
// Update account status
if (login) {
acc->resource().m_status = ClientResource::Connecting;
addAccPendingStatus(*m,acc,stat);
// Make sure we see the login fail notification
acc->m_params.clearParam(YSTRING("internal.nologinfail"));
// Load module ?
checkLoadModule(&acc->params());
}
else {
acc->resource().m_status = ClientResource::Offline;
// Avoid notification when disconnected
acc->m_params.setParam("internal.nologinfail",String::boolText(true));
// Remove account notifications now
removeAccNotifications(acc);
}
acc->resource().setStatusText();
}
else
m = Client::buildNotify(true,acc->toString(),acc->resource(false));
NamedList set("");
NamedList* p = 0;
if (upd)
p = new NamedList("");
else
p = &set;
p->addParam("status_image",resStatusImage(acc->resource().m_status),false);
const char* sName = acc->resource().statusName();
NamedString* status = new NamedString("status",sName);
status->append(acc->resource().m_text,": ");
p->addParam(status);
if (upd)
upd->addParam(new NamedPointer(acc->toString(),p,String::boolText(false)));
else
Client::self()->setTableRow(s_accountList,acc->toString(),p);
if (accounts)
updateAccountStatus(acc,accounts);
Engine::enqueue(m);
}
// Set enabled accounts status from global. Update UI. Notify remote party
static void setAccountsStatus(ClientAccountList* accounts)
{
if (!Client::s_engineStarted)
return;
if (!accounts)
return;
// Update UI
AccountStatus* stat = AccountStatus::current();
AccountStatus::updateUi();
NamedList upd("");
for (ObjList* o = accounts->accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* acc = static_cast<ClientAccount*>(o->get());
if (!acc->startup())
continue;
setAccountStatus(accounts,acc,stat,&upd);
}
if (upd.count())
Client::self()->updateTableRows(s_accountList,&upd);
}
// Login account proxy for DefaultLogic::loginAccount()
// Check password if required
static bool loginAccount(ClientLogic* logic, const NamedList& account, bool login,
bool checkPwd = true)
{
if (login && checkPwd && !account.getValue(YSTRING("password")))
return 0 != getAccPasswordWnd(account,true);
return logic && logic->loginAccount(account,login);
}
// Fill a list used to update a chat contact UI
// data: fill contact name, account ...
// status: fill contact status
static void fillChatContact(NamedList& p, ClientContact& c, bool data, bool status,
bool roomContact = false)
{
if (!roomContact) {
p.addParam("active:chat_send_file",String::boolText(0 != c.findFileTransferResource()));
p.addParam("active:chat_share_file",String::boolText(true));
p.addParam("active:chat_shared_file",String::boolText(c.haveShared()));
}
if (!(data || status))
return;
// Fill status
if (roomContact && c.mucRoom())
p.addParam("type","chatroom");
if (status) {
ClientResource* res = c.status();
int stat = c.online() ? ClientResource::Online : ClientResource::Offline;
if (res)
stat = res->m_status;
String text;
if (!roomContact) {
String img = resStatusImage(stat);
p.addParam("image:status_image",img,false);
p.addParam("name_image",img,false);
if (res)
text = res->m_text;
}
else
p.addParam("name_image",Client::s_skinPath + "muc.png");
p.addParam("status_text",text ? text.c_str() : ClientResource::statusDisplayText(stat));
p.addParam("status",lookup(stat,ClientResource::s_statusName));
}
// Fill other data
if (!data)
return;
p.addParam("account",c.accountName());
p.addParam("name",c.m_name);
p.addParam("contact",c.uri());
p.addParam("subscription",c.subscriptionStr());
if (!c.mucRoom()) {
NamedString* groups = new NamedString("groups");
Client::appendEscape(*groups,c.groups());
p.addParam(groups);
}
else
p.addParam("groups","Chat Rooms");
}
// Enable/disable chat contacts actions
static void enableChatActions(ClientContact* c, bool checkVisible = true, bool global = true,
bool chat = false)
{
if (!Client::valid())
return;
if (chat && c && c->getChatWnd()) {
NamedList p("");
fillChatContact(p,*c,false,false);
c->updateChatWindow(p);
}
if (!global)
return;
// Check if the chat tab is visible
if (c && checkVisible) {
String tab;
Client::self()->getSelect(s_mainwindowTabs,tab);
if (tab != YSTRING("tabChat"))
c = 0;
}
const char* s = String::boolText(c != 0);
bool mucRoom = c && c->mucRoom();
NamedList p("");
p.addParam("active:" + s_chat,s);
p.addParam(s_chat,!mucRoom ? "Chat" : "Join");
p.addParam("active:" + s_chatCall,String::boolText(!mucRoom && c && c->findAudioResource()));
p.addParam("active:" + s_fileSend,String::boolText(!mucRoom && c && c->findFileTransferResource()));
p.addParam("active:" + s_fileShare,s);
p.addParam("active:" + s_fileShared,String::boolText(c && c->haveShared()));
p.addParam("active:" + s_chatShowLog,s);
p.addParam("active:" + s_chatEdit,s);
p.addParam("active:" + s_chatDel,s);
const char* noRoomOk = String::boolText(!mucRoom && c);
p.addParam("active:" + s_chatInfo,noRoomOk);
p.addParam("active:" + s_chatSub,noRoomOk);
p.addParam("active:" + s_chatUnsubd,noRoomOk);
p.addParam("active:" + s_chatUnsub,noRoomOk);
p.addParam("active:" + s_menuSubscription,noRoomOk);
Client::self()->setParams(&p);
}
// Change a contact's docked chat status
// Re-create the chat if the contact has one
static void changeDockedChat(ClientContact& c, bool on)
{
static const String s_histParam("history");
static const String s_itCountParam("_yate_tempitemcount");
static const String s_itReplParam("_yate_tempitemreplace");
if (!c.hasChat()) {
c.setDockedChat(on);
return;
}
// Get chat history and message
String history;
String input;
c.getChatHistory(history,true);
c.getChatInput(input);
// Retrieve properties
String tempItemCount;
String tempItemReplace;
c.getChatProperty(s_histParam,s_itCountParam,tempItemCount);
c.getChatProperty(s_histParam,s_itReplParam,tempItemReplace);
// Re-create chat
c.destroyChatWindow();
c.setDockedChat(on);
c.createChatWindow();
NamedList p("");
fillChatContact(p,c,true,true);
fillChatContactShareStatus(p,c,false,true);
ClientResource* res = c.status();
c.updateChatWindow(p,"Chat [" + c.m_name + "]",
resStatusImage(res ? res->m_status : ClientResource::Offline));
// Set old chat state
c.setChatHistory(history,true);
c.setChatInput(input);
c.setChatProperty(s_histParam,s_itCountParam,tempItemCount);
c.setChatProperty(s_histParam,s_itReplParam,tempItemReplace);
c.showChat(true);
}
// Retrieve the selected item in muc room members list
static MucRoomMember* selectedRoomMember(MucRoom& room)
{
Window* w = room.getChatWnd();
if (!w)
return 0;
NamedList p("");
String tmp("getselect:" + s_mucMembers);
p.addParam(tmp,"");
Client::self()->getTableRow(ClientContact::s_dockedChatWidget,room.resource().toString(),&p,w);
const String& id = p[tmp];
return room.findMemberById(id);
}
// Enable/disable MUC room actions
static void enableMucActions(NamedList& p, MucRoom& room, MucRoomMember* member,
bool roomActions = true)
{
// Room related actions
if (roomActions) {
p.addParam("active:" + s_mucChgSubject,String::boolText(room.canChangeSubject()));
p.addParam("active:" + s_mucChgNick,String::boolText(room.resource().online()));
p.addParam("active:" + s_mucInvite,String::boolText(room.canInvite()));
}
// Member related actions
if (member && !room.ownMember(member)) {
p.addParam("active:" + s_mucPrivChat,String::boolText(room.canChatPrivate()));
p.addParam("active:" + s_mucKick,String::boolText(member->online() && room.canKick(member)));
p.addParam("active:" + s_mucBan,
String::boolText(member->online() && member->m_uri && room.canBan(member)));
}
else {
const char* no = String::boolText(false);
p.addParam("active:" + s_mucPrivChat,no);
p.addParam("active:" + s_mucKick,no);
p.addParam("active:" + s_mucBan,no);
}
}
// Update the status of a MUC room member
static void updateMucRoomMember(MucRoom& room, MucRoomMember& item, Message* msg = 0)
{
NamedList* pList = new NamedList("");
NamedList* pChat = 0;
const char* upd = String::boolText(true);
bool canChat = false;
if (room.ownMember(item.toString())) {
canChat = room.canChat();
fillChatContact(*pList,room,true,true);
pChat = new NamedList(*pList);
// Override some parameters
pChat->setParam("name",room.uri());
pList->setParam("name",item.m_name);
pList->setParam("groups","Me");
// Enable actions
enableMucActions(*pChat,room,selectedRoomMember(room));
if (item.offline()) {
pChat->addParam("room_subject","");
// Mark all members offline
for (ObjList* o = room.resources().skipNull(); o; o = o->skipNext()) {
MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
if (!m->offline()) {
m->m_status = ClientResource::Offline;
updateMucRoomMember(room,*m);
}
}
// Add destroy notification in room chat
if (msg && msg->getBoolValue(YSTRING("muc.destroyed"))) {
String text("Room was destroyed");
// Room destroyed
const char* rr = msg->getValue(YSTRING("muc.destroyreason"));
if (!TelEngine::null(rr))
text << " (" << rr << ")";
const char* altRoom = msg->getValue(YSTRING("muc.alternateroom"));
if (!TelEngine::null(altRoom))
text << "\r\nPlease join " << altRoom;
addChatNotify(room,text,msg->msgTime().sec());
}
}
}
else {
pList->addParam("account",room.accountName());
pList->addParam("name",item.m_name);
pList->addParam("groups",lookup(item.m_role,MucRoomMember::s_roleName));
pList->addParam("status_text",ClientResource::statusDisplayText(item.m_status));
String uri = item.m_uri;
if (uri)
uri.append(item.m_instance,"/");
pList->addParam("contact",uri,false);
String img = resStatusImage(item.m_status);
pList->addParam("image:status_image",img);
pList->addParam("name_image",img);
if (room.hasChat(item.toString())) {
pChat = new NamedList(*pList);
pChat->setParam("name",room.uri() + " - " + item.m_name);
canChat = room.canChatPrivate() && item.online();
}
// Remove offline non members from members list
if (item.offline() && item.m_affiliation <= MucRoomMember::Outcast)
upd = 0;
}
// Update members list in room page
NamedList tmp("");
NamedList* params = new NamedList("");
params->addParam(new NamedPointer(item.toString(),pList,upd));
tmp.addParam(new NamedPointer("updatetablerows:" + s_mucMembers,params));
room.updateChatWindow(room.resource().toString(),tmp);
if (pChat) {
// Enable/disable chat
pChat->addParam("active:" + s_chatSend,String::boolText(canChat));
pChat->addParam("active:message",String::boolText(canChat));
room.updateChatWindow(item.toString(),*pChat);
TelEngine::destruct(pChat);
}
}
// Show a MUC room's chat. Create and initialize it if not found
static void createRoomChat(MucRoom& room, MucRoomMember* member = 0, bool active = true)
{
if (!member)
member = &room.resource();
if (room.hasChat(member->toString())) {
room.showChat(member->toString(),true,active);
return;
}
room.createChatWindow(member->toString());
updateMucRoomMember(room,*member);
if (!room.ownMember(member)) {
room.showChat(member->toString(),true,active);
return;
}
// Build context menu(s)
NamedList tmp("");
// Room context menu
String menuName("menu_" + room.resource().toString());
NamedList* pRoom = new NamedList(menuName);
pRoom->addParam("title","Room");
pRoom->addParam("item:" + s_mucSave,"");
pRoom->addParam("item:","");
pRoom->addParam("item:" + s_mucChgNick,"");
pRoom->addParam("item:" + s_mucChgSubject,"");
pRoom->addParam("item:","");
pRoom->addParam("item:" + s_mucInvite,"");
pRoom->addParam("item:","");
pRoom->addParam("item:" + s_mucRoomShowLog,"");
tmp.addParam(new NamedPointer("setmenu",pRoom,""));
// Members context menu
menuName << "_" << s_mucMembers;
NamedList* pMembers = new NamedList(menuName);
pMembers->addParam("item:" + s_mucPrivChat,"");
pMembers->addParam("item:","");
pMembers->addParam("item:" + s_mucKick,"");
pMembers->addParam("item:" + s_mucBan,"");
pMembers->addParam("item:","");
pMembers->addParam("item:" + s_mucMemberShowLog,"");
NamedList* p = new NamedList("");
p->addParam(new NamedPointer("contactmenu",pMembers));
tmp.addParam(new NamedPointer("setparams:" + s_mucMembers,p));
room.updateChatWindow(room.resource().toString(),tmp);
// Show it
room.showChat(member->toString(),true,active);
}
// Reset a MUC room. Destroy chat window
static void clearRoom(MucRoom* room)
{
if (!room)
return;
if (!room->resource().offline()) {
Engine::enqueue(room->buildJoin(false));
room->resource().setStatus(ClientResource::Offline);
}
room->resource().m_affiliation = MucRoomMember::AffNone;
room->resource().m_role = MucRoomMember::RoleNone;
room->destroyChatWindow();
}
// Show a contact's info window
// Update it and, optionally, activate it
static bool updateContactInfo(ClientContact* c, bool create = false, bool activate = false)
{
static const String s_groups("groups");
static const String s_resources("resources");
if (!c)
return false;
Window* w = getContactInfoEditWnd(false,false,c,create);
if (!w)
return false;
NamedList p("");
p.addParam("title","Contact info [" + c->uri() + "]");
p.addParam("name",c->m_name);
p.addParam("username",c->uri());
p.addParam("account",c->accountName());
p.addParam("subscription",c->subscriptionStr());
Client::self()->setParams(&p,w);
// Add groups
Client::self()->clearTable(s_groups,w);
for (ObjList* o = c->groups().skipNull(); o; o = o->skipNext())
Client::self()->addOption(s_groups,o->get()->toString(),false,String::empty(),w);
// Update resources
Client::self()->clearTable(s_resources,w);
NamedList upd("");
for (ObjList* o = c->resources().skipNull(); o; o = o->skipNext()) {
ClientResource* r = static_cast<ClientResource*>(o->get());
NamedList* l = new NamedList(r->toString());
l->addParam("name",r->m_name);
l->addParam("name_image",resStatusImage(r->m_status),false);
l->addParam("status",r->m_text);
if (r->caps().flag(ClientResource::CapAudio))
l->addParam("audio_image",Client::s_skinPath + "phone.png");
upd.addParam(new NamedPointer(r->toString(),l,String::boolText(true)));
}
Client::self()->updateTableRows(s_resources,&upd,false,w);
// Show the window and activate it
Client::self()->setVisible(w->id(),true,activate);
return true;
}
// Show an edit/add chat contact window
static bool showContactEdit(ClientAccountList& accounts, bool room = false,
ClientContact* c = 0)
{
Window* w = getContactInfoEditWnd(true,room,c,true,true);
if (!w) {
// Activate it if found
w = c ? getContactInfoEditWnd(true,room,c) : 0;
if (w)
Client::self()->setActive(w->id(),true,w);
return w != 0;
}
if (c && c->mucRoom())
room = true;
NamedList p("");
const char* add = String::boolText(c == 0);
const char* edit = String::boolText(c != 0);
if (!room) {
p.addParam("show:chataccount",add);
p.addParam("show:frame_uri",add);
p.addParam("show:chatcontact_account",edit);
p.addParam("show:chatcontact_uri",edit);
Client::self()->clearTable("groups",w);
// Add groups used by all accounts
NamedList upd("");
for (ObjList* o = accounts.accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (!a->hasChat())
continue;
for (ObjList* oc = a->contacts().skipNull(); oc; oc = oc->skipNext()) {
ClientContact* cc = static_cast<ClientContact*>(oc->get());
for (ObjList* og = cc->groups().skipNull(); og; og = og->skipNext()) {
const String& grp = og->get()->toString();
NamedString* param = upd.getParam(grp);
NamedList* p = 0;
if (!param) {
p = new NamedList(grp);
p->addParam("group",grp);
p->addParam("check:group",String::boolText(c == cc));
upd.addParam(new NamedPointer(grp,p,String::boolText(true)));
}
else if (c == cc) {
p = YOBJECT(NamedList,param);
if (p)
p->setParam("check:group",String::boolText(true));
}
}
}
}
Client::self()->updateTableRows(YSTRING("groups"),&upd,false,w);
p.addParam("show:request_subscribe",String::boolText(c == 0));
}
if (c) {
p.addParam("context",c->toString());
String title;
if (!room) {
title = "Edit friend ";
if (c->m_name && (c->m_name != c->uri()))
title << "'" << c->m_name << "' ";
}
else
title = "Edit chat room ";
title << "<" << c->uri() << ">";
p.addParam("title",title);
p.addParam("chatcontact_account",c->accountName());
p.addParam("name",c->m_name);
p.addParam("chatcontact_uri",c->uri());
MucRoom* r = room ? c->mucRoom() : 0;
if (r)
fillRoomParams(p,r);
}
else {
p.addParam("context","");
if (!room) {
p.addParam("title","Add friend");
p.addParam("username","");
p.addParam("domain","");
p.addParam("name","");
p.addParam("check:request_subscribe",String::boolText(true));
}
else {
p.addParam("title","Add chat room");
fillRoomParams(p);
}
}
// Fill accounts. Select single account or room account
if (!c || c->mucRoom()) {
Client::self()->addOption(s_chatAccount,s_notSelected,false,String::empty(),w);
for (ObjList* o = accounts.accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (a->resource().online() && a->hasChat())
Client::self()->addOption(s_chatAccount,a->toString(),false,String::empty(),w);
}
if (!(c && c->mucRoom()))
selectListItem(s_chatAccount,w,false,false);
else
p.addParam("select:" + s_chatAccount,c->accountName());
}
Client::self()->setParams(&p,w);
Client::self()->setVisible(w->id(),true,true);
return true;
}
// Find a temporary wizard
static inline ClientWizard* findTempWizard(Window* wnd)
{
if (!wnd)
return 0;
ObjList* o = s_tempWizards.find(wnd->id());
return o ? static_cast<ClientWizard*>(o->get()) : 0;
}
// Retrieve selected contacts from UI
static void getSelectedContacts(ObjList& list, const String& name, Window* w,
const String& itemToGet)
{
if (!Client::valid())
return;
String param("check:" + itemToGet);
NamedList p("");
Client::self()->getOptions(name,&p,w);
NamedIterator iter(p);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (!ns->name())
continue;
NamedList* tmp = new NamedList(ns->name());
Client::self()->getTableRow(name,*tmp,tmp,w);
if (tmp->getBoolValue(param))
list.append(tmp);
else
TelEngine::destruct(tmp);
}
}
// Show the MUC invite window
static bool showMucInvite(ClientContact& contact, ClientAccountList* accounts)
{
if (!Client::valid())
return false;
Window* w = Client::self()->getWindow(s_wndMucInvite);
if (!w)
return false;
NamedList p("");
MucRoom* room = contact.mucRoom();
if (room) {
p.addParam("invite_room",room->uri());
}
else {
p.addParam("invite_room","");
p.addParam("invite_password","");
}
p.addParam("show:label_room",String::boolText(room != 0));
p.addParam("show:invite_room",String::boolText(room != 0));
p.addParam("show:label_password",String::boolText(room == 0));
p.addParam("show:invite_password",String::boolText(room == 0));
p.addParam("invite_account",contact.accountName());
p.addParam("invite_text","");
String showOffline;
Client::self()->getProperty(s_inviteContacts,YSTRING("_yate_showofflinecontacts"),showOffline,w);
p.addParam("check:muc_invite_showofflinecontacts",showOffline);
Client::self()->setParams(&p,w);
Client::self()->clearTable(s_inviteContacts,w);
if (accounts) {
NamedList rows("");
String sel;
if (!room)
sel = contact.uri();
for (ObjList* oa = accounts->accounts().skipNull(); oa; oa = oa->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(oa->get());
for (ObjList* oc = a->contacts().skipNull(); oc; oc = oc->skipNext()) {
ClientContact* c = static_cast<ClientContact*>(oc->get());
int stat = contactStatus(*c);
String id = c->uri();
// Check if contact already added
NamedString* added = rows.getParam(id);
if (added) {
NamedList* nl = YOBJECT(NamedList,added);
int aStat = nl ? nl->getIntValue(YSTRING("contact_status_value")) :
ClientResource::Unknown;
// At least one of them offline: the greater status wins
if ((aStat < ClientResource::Online ||
stat < ClientResource::Online) && stat < aStat)
continue;
// Both online: the lesser status wins
if (stat >= aStat)
continue;
rows.clearParam(added);
}
// Add the contact
NamedList* p = new NamedList(id);
fillChatContact(*p,*c,true,true);
p->addParam("contact_status_value",String(stat));
if (id == sel)
p->addParam("check:name",String::boolText(true));
rows.addParam(new NamedPointer(id,p,String::boolText(true)));
}
}
Client::self()->updateTableRows(s_inviteContacts,&rows,false,w);
if (sel)
Client::self()->setSelect(s_inviteContacts,sel,w);
}
Client::self()->setVisible(s_wndMucInvite,true,true);
return true;
}
// Build a muc.room message
static Message* buildMucRoom(const char* oper, const String& account, const String& room,
const char* reason = 0, const char* contact = 0)
{
Message* m = Client::buildMessage("muc.room",account,oper);
m->addParam("room",room,false);
m->addParam("contact",contact,false);
m->addParam("reason",reason,false);
return m;
}
// Show advanced UI controls
// Conditionally show call account selector and set its selection
static void setAdvancedMode(bool* show = 0)
{
if (!Client::valid())
return;
bool ok = show ? *show : Client::s_settings.getBoolValue("client","advanced_mode");
const char* val = String::boolText(ok);
NamedList p("");
p.addParam("check:advanced_mode",val);
p.addParam("show:frame_call_protocol",val);
// Show/hide account selector. Select single account
bool showAcc = ok;
NamedString* account = 0;
NamedList accounts("");
Client::self()->getOptions(s_account,&accounts);
for (unsigned int i = accounts.length(); i > 0; i--) {
NamedString* ns = accounts.getParam(i - 1);
if (!ns || Client::s_notSelected.matches(ns->name()))
continue;
if (!account)
account = ns;
else {
account = 0;
showAcc = true;
break;
}
}
p.addParam("show:frame_call_account",String::boolText(showAcc));
if (account)
p.addParam("select:" + s_account,account->name());
Client::self()->setParams(&p);
}
// Open a choose file dialog used to send/receive file(s)
static bool chooseFileTransfer(bool send, const String& action, Window* w,
const char* file = 0)
{
static const String s_allFilesFilter = "All files (*)";
if (!Client::valid())
return false;
NamedList p("");
p.addParam("action",action);
p.addParam("dir",s_lastFileDir,false);
if (send) {
String filters;
filters << "Image files (*.jpg *.jpeg *.png *bmp *gif *.tiff *.tif)";
filters << "|Video files (*.avi *.divx *.xvid *.mpg *.mpeg)";
filters << "|Portable Document Format files (*.pdf)";
filters << "|" << s_allFilesFilter;
p.addParam("filters",filters);
p.addParam("caption","Choose file to send");
p.addParam("selectedfilter",s_lastFileFilter ? s_lastFileFilter : s_allFilesFilter);
}
else {
p.addParam("save",String::boolText(true));
p.addParam("selectedfile",file,false);
p.addParam("chooseanyfile",String::boolText(true));
}
return Client::self()->chooseFile(w,p);
}
// Open a choose directory dialog
static bool chooseDir(const String& action, Window* w)
{
if (!Client::valid())
return false;
NamedList p("");
p.addParam("choosefile",String::boolText(false));
p.addParam("action",action);
p.addParam("dir",s_lastFileShareDir,false);
p.addParam("caption","Choose directory");
return Client::self()->chooseFile(w,p);
}
// Choose a directory to share with a given contact
static bool chooseDirShareDir(ClientAccountList* accounts, Window* w,
const String& cId = String::empty())
{
if (!(accounts && Client::valid()))
return false;
ClientContact* c = 0;
if (cId)
c = accounts->findContact(cId);
else if (w)
c = accounts->findContact(w->context());
return c && chooseDir(s_fileShareChooseDirPrefix + c->toString(),w);
}
// Handle set shared directory for a contact
static bool handleShareSet(bool isDir, ClientAccountList* accounts, const String& cId, Window* w,
const NamedList* params, bool fromFileChoose = true)
{
if (!isDir) {
DDebug(DebugFail,"handleShareSet not implemented for non dir");
return false;
}
if (!(accounts && cId && params && Client::valid()))
return false;
if (fromFileChoose) {
const String& dir = (*params)[YSTRING("dir")];
if (dir != s_lastFileShareDir) {
s_lastFileShareDir = dir;
Client::s_settings.setValue("filetransfer","share_dir",s_lastFileShareDir);
}
}
ClientContact* c = accounts->findContact(cId);
if (!c)
return false;
bool changed = false;
bool hadShare = c->haveShare();
NamedIterator iter(*params);
NamedList upd("");
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (!*ns)
continue;
if (fromFileChoose) {
if (ns->name() != YSTRING("file"))
continue;
}
else {
if (ns->name() != YSTRING("drop:directory"))
continue;
if (!File::exists(*ns))
continue;
}
String path;
if (!Client::removeEndsWithPathSep(path,*ns))
continue;
// NOTE: Check item type (dir/file) if we share files (now we share only directories),
// remove from UI and update if item type changes
// Already in the list?
if (Client::self()->getTableRow(s_fileShareList,path,0,w))
continue;
if (!c->setShareDir(String::empty(),path,false))
continue;
changed = true;
NamedList* p = buildShareItemUI(c,path);
if (p)
upd.addParam(new NamedPointer(path,p,String::boolText(true)));
updateContactShareInfo(c,true,&path);
}
if (changed) {
c->saveShare();
Client::self()->updateTableRows(s_fileShareList,&upd,false,w);
if (hadShare != c->haveShare())
updateContactShareStatus(*c);
notifyContactShareInfoChanged(c);
}
return true;
}
// Handle set shared directory for a contact
static bool handleShareDel(ClientAccountList* accounts, const String& cId, Window* w)
{
if (!(w && accounts && cId && Client::valid()))
return false;
ClientContact* c = accounts->findContact(cId);
if (!c)
return false;
NamedList sel("");
if (!Client::self()->getSelect(s_fileShareList,sel,w))
return true;
if (0 == sel.getParam(0))
return true;
updateContactShareInfo(c,false,0,&sel);
bool changed = false;
bool hadShare = c->haveShare();
NamedIterator iter(sel);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
changed = c->removeShare(ns->name(),false) || changed;
// Clear param value to signal delete when updating
*((NamedString*)ns) = "";
}
if (sel.getParam(0))
Client::self()->updateTableRows(s_fileShareList,&sel,false,w);
if (changed) {
c->saveShare();
if (hadShare != c->haveShare())
updateContactShareStatus(*c);
notifyContactShareInfoChanged(c);
}
return true;
}
// Begin edit selected item in a list
static bool beginEditSelected(const String& name, Window* w, const char* what = 0)
{
if (!(w && Client::valid()))
return false;
String sel;
Client::self()->getSelect(name,sel,w);
if (!sel)
return false;
NamedList p(name);
p.addParam("beginedit:" + sel,what);
return Client::self()->setParams(&p,w);
}
// Add a tray icon to the mainwindow stack
static bool addTrayIcon(const String& type)
{
if (!type)
return false;
int prio = 0;
String triggerAction;
bool doubleClickAction = true;
NamedList* iconParams = 0;
String name;
name << "mainwindow_" << type << "_icon";
const char* specific = 0;
String info = "Yate Client";
if (type == "main") {
prio = Client::TrayIconMain;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "null_team-32.png");
triggerAction = "action_toggleshow_mainwindow";
doubleClickAction = false;
}
else if (type == "incomingcall") {
prio = Client::TrayIconIncomingCall;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "tray_incomingcall.png");
info << "\r\nAn incoming call is waiting";
triggerAction = s_actionShowCallsList;
specific = "View calls";
}
else if (type == "notification" || type == "info") {
iconParams = new NamedList(name);
if (type == "notification") {
prio = Client::TrayIconNotification;
iconParams->addParam("icon",Client::s_skinPath + "tray_notification.png");
triggerAction = s_actionShowNotification;
}
else {
prio = Client::TrayIconInfo;
iconParams->addParam("icon",Client::s_skinPath + "tray_info.png");
triggerAction = s_actionShowInfo;
}
info << "\r\nA notification is requiring your attention";
specific = "View notifications";
}
else if (type == "incomingchat") {
prio = Client::TrayIconIncomingChat;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "tray_incomingchat.png");
info << "\r\nYou have unread chat";
triggerAction = s_actionPendingChat;
specific = "View chat";
}
if (!iconParams)
return false;
iconParams->addParam("tooltip",info);
iconParams->addParam("dynamicActionTrigger:string",triggerAction,false);
if (doubleClickAction)
iconParams->addParam("dynamicActionDoubleClick:string",triggerAction,false);
// Add the menu
NamedList* pMenu = new NamedList("menu_" + type);
pMenu->addParam("item:quit","Quit");
pMenu->addParam("image:quit",Client::s_skinPath + "quit.png");
pMenu->addParam("item:","");
pMenu->addParam("item:action_show_mainwindow","Show application");
pMenu->addParam("image:action_show_mainwindow",Client::s_skinPath + "null_team-32.png");
if (prio != Client::TrayIconMain && triggerAction && specific) {
pMenu->addParam("item:","");
pMenu->addParam("item:" + triggerAction,specific);
pMenu->addParam("image:" + triggerAction,iconParams->getValue("icon"));
}
iconParams->addParam(new NamedPointer("menu",pMenu));
return Client::addTrayIcon(YSTRING("mainwindow"),prio,iconParams);
}
// Remove a tray icon from mainwindow stack
static inline bool removeTrayIcon(const String& type)
{
return type &&
Client::removeTrayIcon(YSTRING("mainwindow"),"mainwindow_" + type + "_icon");
}
// Notify incoming chat to the user
static void notifyIncomingChat(ClientContact* c, const String& id = String::empty())
{
if (!(c && Client::valid()))
return;
MucRoom* room = c->mucRoom();
if (!room) {
if (c->isChatActive())
return;
c->flashChat();
}
else {
if (!id || room->isChatActive(id))
return;
room->flashChat(id);
}
const String& str = !room ? c->toString() : id;
if (!s_pendingChat.find(str))
s_pendingChat.append(new String(str));
addTrayIcon(YSTRING("incomingchat"));
}
// Show the first chat item in pending chat
static void showPendingChat(ClientAccountList* accounts)
{
if (!(accounts && Client::valid()))
return;
bool tryAgain = true;
while (tryAgain) {
String* id = static_cast<String*>(s_pendingChat.remove(false));
if (!s_pendingChat.skipNull()) {
removeTrayIcon(YSTRING("incomingchat"));
tryAgain = false;
}
if (!id)
break;
ClientContact* c = accounts->findContact(*id);
MucRoom* room = !c ? accounts->findRoomByMember(*id) : 0;
if (c) {
if (c->hasChat()) {
c->flashChat(false);
c->showChat(true,true);
}
else
c = 0;
}
else if (room) {
if (room->hasChat(*id)) {
room->flashChat(*id,false);
room->showChat(*id,true,true);
}
else
room = 0;
}
TelEngine::destruct(id);
tryAgain = !(c || room);
}
}
// Remove an item from pending chat
// Stop flashing it if a list is given
static void removePendingChat(const String& id, ClientAccountList* accounts = 0)
{
if (!(id && Client::valid()))
return;
s_pendingChat.remove(id);
if (!s_pendingChat.skipNull())
removeTrayIcon(YSTRING("incomingchat"));
if (!accounts)
return;
ClientContact* c = accounts->findContact(id);
MucRoom* room = !c ? accounts->findRoomByMember(id) : 0;
if (c)
c->flashChat(false);
else if (room)
room->flashChat(id,false);
}
// Set offline to MUCs belonging to a given account
static void setOfflineMucs(ClientAccount* acc)
{
if (!acc || Client::exiting())
return;
for (ObjList* o = acc->mucs().skipNull(); o; o = o->skipNext()) {
MucRoom* room = static_cast<MucRoom*>(o->get());
if (room->resource().offline())
continue;
room->resource().m_status = ClientResource::Offline;
room->resource().m_affiliation = MucRoomMember::AffNone;
room->resource().m_role = MucRoomMember::RoleNone;
updateMucRoomMember(*room,room->resource());
}
}
// Update telephony account selector(s)
static void updateTelAccList(bool ok, ClientAccount* acc)
{
if (!acc)
return;
DDebug(ClientDriver::self(),DebugAll,"updateTelAccList(%d,%p)",ok,acc);
if (ok && (isTelProto(acc->protocol()) || isGmailAccount(acc) || isTigaseImAccount(acc)))
Client::self()->updateTableRow(s_account,acc->toString());
else
Client::self()->delTableRow(s_account,acc->toString());
}
// Query roster on a given account
static bool queryRoster(ClientAccount* acc)
{
if (!acc)
return false;
Message* m = Client::buildMessage("user.roster",acc->toString(),"query");
m->copyParams(acc->params(),YSTRING("protocol"));
return Engine::enqueue(m);
}
/**
* ClientWizard
*/
ClientWizard::ClientWizard(const String& wndName, ClientAccountList* accounts, bool temp)
: String(wndName),
m_accounts(accounts),
m_temp(temp)
{
if (!temp)
return;
// Build a temporary window
String name = wndName;
name << (unsigned int)Time::msecNow();
assign(name);
if (Client::valid())
Client::self()->createWindowSafe(wndName,name);
Window* w = window();
if (w)
Client::self()->setProperty(*this,YSTRING("_yate_destroyonhide"),String::boolText(true),w);
}
// Handle actions from user interface
bool ClientWizard::action(Window* w, const String& name, NamedList* params)
{
if (!isWindow(w))
return false;
XDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s)::action(%s,%p) [%p]",
c_str(),name.c_str(),params,this);
if (name == s_actionNext) {
onNext();
return true;
}
if (name == s_actionPrev) {
onPrev();
return true;
}
if (name == s_actionCancel) {
onCancel();
return true;
}
return false;
}
// Handle checkable widgets status changes in wizard window
bool ClientWizard::toggle(Window* w, const String& name, bool active)
{
if (!isWindow(w))
return false;
XDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s)::toggle(%s,%u) [%p]",
c_str(),name.c_str(),active,this);
if (name == YSTRING("window_visible_changed")) {
windowVisibleChanged(active);
return false;
}
return false;
}
// Handle user.notify messages. Restart the wizard if the operating account is offline
// Return true if handled
bool ClientWizard::handleUserNotify(const String& account, bool ok, const char* reason)
{
if (!(m_account && m_account == account))
return false;
DDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s)::handleUserNotify(%s,%u)",
c_str(),account.c_str(),ok);
if (ok)
return true;
reset(true);
if (Client::valid() && Client::self()->getVisible(toString())) {
start();
showError(window(),
"The selected account is offline.\r\nChoose another one or close the wizard");
}
return true;
}
// Retrieve the selected account
ClientAccount* ClientWizard::account(const String& list)
{
Window* w = m_accounts ? window() : 0;
ClientAccount* acc = w ? selectedAccount(*m_accounts,w,list) : 0;
if (acc)
m_account = acc->toString();
else
m_account.clear();
XDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s) current account is %s",
c_str(),m_account.c_str());
return acc;
}
// Update next/prev actions active status
void ClientWizard::updateActions(NamedList& p, bool canPrev, bool canNext, bool canCancel)
{
p.addParam("active:" + s_actionPrev,String::boolText(canPrev));
p.addParam("active:" + s_actionNext,String::boolText(canNext));
p.addParam("active:" + s_actionCancel,String::boolText(canCancel));
}
/*
* AccountWizard
*/
// Release the account. Disconnect it if deleted
void AccountWizard::reset(bool full)
{
if (!m_account)
return;
if (full && m_accounts) {
if (!(Engine::exiting() || Client::exiting())) {
ClientAccount* acc = account();
if (acc) {
Engine::enqueue(userLogin(acc,false));
acc->m_params.setParam("internal.nologinfail",String::boolText(true));
}
}
m_accounts->removeAccount(m_account);
}
DDebug(ClientDriver::self(),DebugAll,
"AccountWizard(%s) reset account delObj=%u",c_str(),full);
m_account.clear();
}
// Handle user.notify messages
bool AccountWizard::handleUserNotify(const String& account, bool ok,
const char* reason)
{
if (!m_account || m_account != account)
return false;
DDebug(ClientDriver::self(),DebugAll,"AccountWizard(%s)::handleUserNotify(%s,%u)",
c_str(),account.c_str(),ok);
String s;
if (ok)
s << "Succesfully created account '" << account << "'";
else {
s << "Failed to connect account '" << account << "'";
s.append(reason,"\r\n");
}
Window* w = window();
if (w) {
NamedList p("");
p.addParam("accwiz_result",s);
updateActions(p,!ok,false,false);
Client::self()->setParams(&p,w);
}
reset(!ok);
return true;
}
void AccountWizard::onNext()
{
String page;
currentPage(page);
if (!page)
return;
if (page == YSTRING("pageAccType"))
changePage(YSTRING("pageServer"),page);
else if (page == YSTRING("pageServer")) {
// Check if we have a host (domain or server)
String host;
if (getAccount(window(),0,0,&host))
changePage(YSTRING("pageAccount"),page);
}
else if (page == YSTRING("pageAccount")) {
if (!m_accounts)
return;
Window* w = window();
// Check if we have a valid, non duplicate account
String proto, user, host;
if (getAccount(w,&proto,&user,&host)) {
if (!m_accounts->findAccount(URI(proto,user,host)))
changePage(YSTRING("pageConnect"),page);
else
showAccDupError(w);
}
}
}
void AccountWizard::onPrev()
{
String page;
currentPage(page);
if (page == YSTRING("pageServer"))
changePage(YSTRING("pageAccType"),page);
else if (page == YSTRING("pageAccount"))
changePage(YSTRING("pageServer"),page);
else if (page == YSTRING("pageConnect"))
changePage(YSTRING("pageAccount"),page);
}
void AccountWizard::onCancel()
{
handleUserNotify(m_account,false,"Cancelled");
}
// Change the wizard page
bool AccountWizard::changePage(const String& page, const String& old)
{
Window* w = window();
if (!w)
return false;
// Provider name (update server page after resetting it)
String provName;
const char* nextText = "Next";
bool canPrev = true;
bool canNext = true;
bool canCancel = false;
NamedList p("");
// Use a do {} while() to break to the end and set page
do {
if (!page || page == YSTRING("pageAccType")) {
canPrev = false;
// Init all wizard if first show
if (old)
break;
p.addParam("check:acc_type_telephony",String::boolText(true));
p.addParam("check:acc_type_gtalk",String::boolText(false));
p.addParam("check:acc_type_facebook",String::boolText(false));
p.addParam("check:acc_type_im",String::boolText(false));
p.addParam("check:acc_register",String::boolText(false));
break;
}
if (page == YSTRING("pageServer")) {
// Don't reset the page if not comming from previous
if (old && old != YSTRING("pageAccType"))
break;
bool tel = true;
Client::self()->getCheck(YSTRING("acc_type_telephony"),tel,w);
// Fill protocols
Client::self()->clearTable(s_accWizProtocol,w);
String proto;
updateProtocolList(w,s_accWizProtocol,&tel,&p,&proto);
// Fill providers
Client::self()->clearTable(s_accWizProviders,w);
Client::self()->addOption(s_accWizProviders,s_notSelected,false,String::empty(),w);
unsigned int n = Client::s_providers.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = Client::s_providers.getSection(i);
if (sect && sect->getBoolValue(YSTRING("enabled"),true))
updateProvidersItem(w,s_accWizProviders,*sect,&tel);
}
Client::self()->setSelect(s_accWizProviders,s_notSelected,w);
// Select provider
bool prov = false;
Client::self()->getCheck(YSTRING("acc_type_gtalk"),prov,w);
if (Client::self()->getCheck(YSTRING("acc_type_gtalk"),prov,w) && prov)
provName = "GTalk";
else if (Client::self()->getCheck(YSTRING("acc_type_facebook"),prov,w) && prov)
provName = "Facebook";
else {
// Show/hide the advanced page
bool adv = false;
Client::self()->getCheck(YSTRING("acc_showadvanced"),adv,w);
selectProtocolSpec(p,proto,adv,s_accWizProtocol);
}
if (provName && !Client::self()->setSelect(s_accWizProviders,provName,w)) {
showError(w,"Provider data not found for selected account type!");
return false;
}
break;
}
if (page == YSTRING("pageAccount")) {
nextText = "Login";
// Don't reset the page if not comming from previous
if (old && old != YSTRING("pageServer"))
break;
p.addParam("acc_username","");
p.addParam("acc_password","");
break;
}
if (page == YSTRING("pageConnect")) {
if (!m_accounts || m_account)
return false;
Window* w = window();
if (!w)
return false;
NamedList a("");
if (!getAccount(w,a,*m_accounts))
return false;
// Build account. Start login
ClientAccount* acc = new ClientAccount(a);
if (!m_accounts->appendAccount(acc)) {
showAccDupError(w);
TelEngine::destruct(acc);
return false;
}
m_account = a;
setAccountContact(acc);
Message* m = userLogin(acc,true);
checkLoadModule(&acc->params());
addAccPendingStatus(*m,acc);
m->addParam("send_presence",String::boolText(false));
m->addParam("request_roster",String::boolText(false));
acc->resource().m_status = ClientResource::Connecting;
TelEngine::destruct(acc);
Engine::enqueue(m);
p.addParam("accwiz_result","Connecting ...");
canPrev = canNext = false;
canCancel = true;
break;
}
return false;
}
while (false);
p.addParam(s_actionNext,nextText,false);
p.addParam("select:" + s_pagesWidget,page ? page : "pageAccType");
updateActions(p,canPrev,canNext,canCancel);
Client::self()->setParams(&p,w);
if (provName)
handleProtoProvSelect(w,s_accWizProviders,provName);
return true;
}
/*
* JoinMucWizard
*/
JoinMucWizard::JoinMucWizard(ClientAccountList* accounts, NamedList* tempParams)
: ClientWizard("joinmucwizard",accounts,tempParams != 0),
m_add(false),
m_queryRooms(false),
m_querySrv(false)
{
if (!tempParams)
return;
reset(true);
Window* w = window();
if (!w)
return;
Client::self()->setParams(tempParams,w);
Client::self()->setShow(YSTRING("room_autojoin"),false,w);
changePage(YSTRING("pageJoinRoom"));
Client::self()->setVisible(toString(),true,true);
}
// Start the wizard. Show the window
void JoinMucWizard::start(bool add)
{
reset(true);
changePage(String::empty());
Window* w = window();
if (!w)
return;
m_add = add;
NamedList p("");
const char* addOk = String::boolText(add);
if (!add)
p.addParam("title","Join Chat Room Wizard");
else
p.addParam("title","Add Chat Room Wizard");
p.addParam("show:room_autojoin",addOk);
Client::self()->setParams(&p,w);
Client::self()->setVisible(toString(),true,true);
}
void JoinMucWizard::reset(bool full)
{
selectListItem(s_mucAccounts,window());
m_account.clear();
m_lastPage.clear();
setQuerySrv(false);
setQueryRooms(false);
}
// Handle actions from wizard window. Return true if handled
bool JoinMucWizard::action(Window* w, const String& name, NamedList* params)
{
if (!(Client::valid() && isWindow(w)))
return false;
if (ClientWizard::action(w,name,params))
return true;
// Query MUC services
if (name == YSTRING("muc_query_servers")) {
// Cancel
if (m_querySrv) {
setQuerySrv(false);
return true;
}
ClientAccount* acc = account();
if (!acc)
return true;
String domain;
Client::self()->getText(YSTRING("muc_domain"),domain,false,w);
Message* m = Client::buildMessage("contact.info",acc->toString(),"queryitems");
if (!domain && acc->contact())
domain = acc->contact()->uri().getHost();
m->addParam("contact",domain,false);
Engine::enqueue(m);
setQuerySrv(true,domain);
m_requests.clear();
m_requests.append(new String(domain));
return true;
}
if (name == YSTRING("textchanged")) {
const String& sender = params ? (*params)[YSTRING("sender")] : String::empty();
if (!sender)
return true;
const String& text = (*params)[YSTRING("text")];
if (sender == YSTRING("muc_server") || sender == YSTRING("room_room")) {
String page;
currentPage(page);
if (page == YSTRING("pageMucServer")) {
if (!checkUriTextChanged(w,sender,text,sender))
return false;
updatePageMucServerNext();
}
return true;
}
return false;
}
return false;
}
// Handle selection changes notifications. Return true if handled
bool JoinMucWizard::select(Window* w, const String& name, const String& item,
const String& text)
{
if (!isWindow(w))
return false;
XDebug(ClientDriver::self(),DebugAll,"JoinMucWizard(%s)::select(%s,%s,%s)",
c_str(),name.c_str(),item.c_str(),text.c_str());
if (name == s_mucAccounts) {
account(s_mucAccounts);
String page;
currentPage(page);
if (page == YSTRING("pageAccount")) {
NamedList p("");
updateActions(p,false,!m_account.null(),false);
Client::self()->setParams(&p,w);
}
return true;
}
if (name == YSTRING("muc_rooms")) {
updatePageMucServerNext();
return true;
}
return false;
}
// Handle checkable widgets status changes in wizard window
// Return true if handled
bool JoinMucWizard::toggle(Window* w, const String& name, bool active)
{
if (!isWindow(w))
return false;
if (name == YSTRING("mucserver_joinroom") || name == YSTRING("mucserver_queryrooms")) {
if (!active)
return true;
String page;
currentPage(page);
if (page == YSTRING("pageMucServer"))
updatePageMucServerNext();
return true;
}
return ClientWizard::toggle(w,name,active);
}
// Process contact.info message
bool JoinMucWizard::handleContactInfo(Message& msg, const String& account,
const String& oper, const String& contact)
{
if (m_temp)
return false;
if (!m_account || m_account != account)
return false;
bool ok = (oper == YSTRING("result"));
if (!ok && oper != YSTRING("error"))
return false;
const String& req = msg[YSTRING("requested_operation")];
bool info = (req == YSTRING("queryinfo"));
if (!info && req != YSTRING("queryitems"))
return false;
ObjList* o = m_requests.find(contact);
if (!o)
return false;
DDebug(ClientDriver::self(),DebugAll,
"JoinMucWizard(%s) handleContactInfo() contact=%s oper=%s req=%s",
c_str(),contact.c_str(),oper.c_str(),req.c_str());
if (!info && m_queryRooms) {
Window* w = ok ? window() : 0;
if (w) {
NamedList upd("");
int n = msg.getIntValue(YSTRING("item.count"));
for (int i = 1; i <= n; i++) {
String pref("item." + String(i));
const String& item = msg[pref];
if (!item)
continue;
NamedList* p = new NamedList("");
p->addParam("room",item);
p->addParam("name",msg.getValue(pref + ".name"),false);
upd.addParam(new NamedPointer(item,p,String::boolText(true)));
}
Client::self()->updateTableRows("muc_rooms",&upd,false,w);
}
if (!(ok && msg.getBoolValue(YSTRING("partial")))) {
o->remove();
setQueryRooms(false);
}
return true;
}
if (!m_querySrv)
return false;
if (info) {
if (ok && contact && msg.getBoolValue(YSTRING("caps.muc"))) {
Window* w = window();
if (w)
Client::self()->updateTableRow(YSTRING("muc_server"),contact,0,false,w);
}
}
else if (ok) {
NamedList upd("");
int n = msg.getIntValue(YSTRING("item.count"));
for (int i = 1; i <= n; i++) {
String pref("item." + String(i));
const String& item = msg[pref];
if (!item)
continue;
DDebug(ClientDriver::self(),DebugAll,
"JoinMucWizard(%s) requesting info from %s",c_str(),item.c_str());
Message* m = Client::buildMessage("contact.info",m_account,"queryinfo");
m->addParam("contact",item,false);
Engine::enqueue(m);
m_requests.append(new String(item));
}
}
if (!(ok && msg.getBoolValue(YSTRING("partial"))))
o->remove();
if (!o->skipNull())
setQuerySrv(false);
return true;
}
// Handle user.notify messages. Update the accounts list
bool JoinMucWizard::handleUserNotify(const String& account, bool ok, const char* reason)
{
if (!m_accounts || m_temp)
return false;
ClientAccount* acc = m_accounts->findAccount(account);
// Add/remove to/from MUC
if (!(acc && acc->hasChat()))
return false;
Window* w = window();
if (!w)
return false;
if (ok)
Client::self()->updateTableRow(s_mucAccounts,account,0,false,w);
else {
ClientWizard::account(s_mucAccounts);
// Avoid showing another selected account
if (m_account && m_account == account)
Client::self()->setSelect(s_mucAccounts,s_notSelected,w);
Client::self()->delTableRow(s_mucAccounts,account,w);
}
if (m_account && m_account == account)
return ClientWizard::handleUserNotify(account,ok,reason);
return true;
}
// Join MUC room wizard
void JoinMucWizard::onNext()
{
String page;
currentPage(page);
if (!page)
return;
if (page == YSTRING("pageAccount")) {
if (!m_add)
changePage(YSTRING("pageChooseRoomServer"),page);
else
changePage(YSTRING("pageMucServer"),page);
}
else if (page == YSTRING("pageChooseRoomServer")) {
bool join = false;
Window* w = window();
if (w && Client::self()->getCheck(YSTRING("muc_use_saved_room"),join,w))
changePage(join ? YSTRING("pageJoinRoom") : YSTRING("pageMucServer"),page);
}
else if (page == YSTRING("pageMucServer")) {
Window* w = window();
bool join = true;
if (w && Client::self()->getCheck(YSTRING("mucserver_joinroom"),join,w))
changePage(join ? YSTRING("pageJoinRoom") : YSTRING("pageRooms"),page);
}
else if (page == YSTRING("pageRooms"))
changePage(YSTRING("pageJoinRoom"),page);
else if (page == YSTRING("pageJoinRoom"))
joinRoom();
}
void JoinMucWizard::onPrev()
{
String page;
currentPage(page);
if (page == YSTRING("pageChooseRoomServer"))
changePage(YSTRING("pageAccount"),page);
else if (page == YSTRING("pageMucServer")) {
if (!m_add)
changePage(YSTRING("pageChooseRoomServer"),page);
else
changePage(YSTRING("pageAccount"),page);
}
else if (page == YSTRING("pageJoinRoom"))
changePage(m_lastPage,page);
else if (page == YSTRING("pageRooms"))
changePage(YSTRING("pageMucServer"),page);
}
void JoinMucWizard::onCancel()
{
if (isCurrentPage(YSTRING("pageMucServer")))
setQuerySrv(false);
else if (isCurrentPage(YSTRING("pageRooms")))
setQueryRooms(false);
}
// Change the wizard page
bool JoinMucWizard::changePage(const String& page, const String& old)
{
Window* w = window();
if (!w)
return false;
const char* nextText = "Next";
bool canPrev = true;
bool canNext = true;
bool canCancel = false;
NamedList p("");
// Use a do {} while() to break to the end and set page
do {
if (!page || page == YSTRING("pageAccount")) {
canPrev = false;
if (!old) {
Client::self()->updateTableRow(s_mucAccounts,s_notSelected,0,true,w);
selectListItem(s_mucAccounts,window());
}
canNext = (0 != account(s_mucAccounts));
break;
}
if (page == YSTRING("pageChooseRoomServer")) {
ClientAccount* a = account(s_mucAccounts);
if (old == YSTRING("pageAccount") && !a)
return showAccSelect(w);
// Add rooms from account
Client::self()->clearTable(s_mucSavedRooms,w);
if (a) {
for (ObjList* o = a->mucs().skipNull(); o; o = o->skipNext()) {
MucRoom* r = static_cast<MucRoom*>(o->get());
if (r->local() || r->remote())
Client::self()->updateTableRow(s_mucSavedRooms,r->uri(),0,false,w);
}
}
// Add saved rooms
unsigned int n = s_mucRooms.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_mucRooms.getSection(i);
if (sect)
Client::self()->updateTableRow(s_mucSavedRooms,*sect,0,false,w);
}
bool useSaved = true;
if (w) {
String tmp;
Client::self()->getSelect(s_mucSavedRooms,tmp,w);
useSaved = !tmp.null();
}
if (useSaved)
p.addParam("check:muc_use_saved_room",String::boolText(true));
else
p.addParam("check:muc_choose_server",String::boolText(true));
break;
}
if (page == YSTRING("pageMucServer")) {
setQuerySrv(false);
setQueryRooms(false);
canNext = selectedMucServer();
// Reset the page if comming from previous
if (old == YSTRING("pageChooseRoomServer") || old == YSTRING("pageAccount")) {
p.addParam("check:mucserver_joinroom",String::boolText(true));
p.addParam("room_room","");
}
break;
}
if (page == YSTRING("pageRooms")) {
// Request rooms
if (old != YSTRING("pageMucServer"))
break;
ClientAccount* acc = account();
if (!acc)
return false;
String target;
selectedMucServer(&target);
if (target) {
Client::self()->clearTable(YSTRING("muc_rooms"),w);
Message* m = Client::buildMessage("contact.info",acc->toString(),"queryitems");
m->addParam("contact",target);
Engine::enqueue(m);
m_requests.clear();
m_requests.append(new String(target));
}
else {
showError(w,"You must choose a MUC server");
return false;
}
break;
}
if (page == YSTRING("pageJoinRoom")) {
if (m_temp) {
canPrev = false;
nextText = "Join";
break;
}
ClientAccount* acc = account();
if (!acc)
return false;
String room;
String server;
String nick;
String pwd;
MucRoom* r = 0;
if (old == YSTRING("pageRooms")) {
String sel;
Client::self()->getSelect("muc_rooms",sel,w);
splitContact(sel,room,server);
}
else if (old == YSTRING("pageMucServer")) {
Client::self()->getText(YSTRING("room_room"),room,false,w);
selectedMucServer(&server);
}
else if (old == YSTRING("pageChooseRoomServer")) {
String tmp;
Client::self()->getSelect(s_mucSavedRooms,tmp,w);
if (!tmp)
return false;
r = acc->findRoomByUri(tmp);
if (r && !(r->local() || r->remote()))
r = 0;
if (r) {
room = r->uri().getUser();
server = r->uri().getHost();
}
else {
NamedList* sect = s_mucRooms.getSection(tmp);
if (sect) {
splitContact(*sect,room,server);
nick = (*sect)[YSTRING("nick")];
pwd = (*sect)[YSTRING("password")];
}
if (!(room && server)) {
Client::self()->delTableRow(s_mucSavedRooms,tmp,w);
s_mucRooms.clearSection(tmp);
s_mucRooms.save();
showError(w,"Deleted unknown/invalid room");
return false;
}
}
}
if (!checkUri(w,room,server,true))
return false;
fillRoomParams(p,r,false);
if (!r) {
p.setParam("room_account",acc->toString());
p.setParam("room_uri",room + "@" + server);
if (!nick && acc->contact())
nick = acc->contact()->uri().getUser();
p.setParam("room_nick",nick);
p.setParam("room_password",pwd);
}
nextText = "Join";
break;
}
return false;
}
while (false);
p.addParam(s_actionNext,nextText,false);
p.addParam("select:" + s_pagesWidget,page ? page.c_str() : "pageAccount");
if (page != YSTRING("pageRooms"))
updateActions(p,canPrev,canNext,canCancel);
Client::self()->setParams(&p,w);
if (page == YSTRING("pageRooms")) {
String target;
bool on = (old == YSTRING("pageMucServer"));
if (on)
selectedMucServer(&target);
setQueryRooms(on,target);
}
else if (page == YSTRING("pageMucServer"))
updatePageMucServerNext();
// Safe to remember the last page here: it might be the received page
m_lastPage = old;
return true;
}
// Handle the join room action
void JoinMucWizard::joinRoom()
{
Window* w = window();
if (!w)
return;
ClientAccount* acc = 0;
if (!m_temp)
acc = account();
else if (m_accounts) {
String tmp;
Client::self()->getText(YSTRING("room_account"),tmp,false,w);
acc = tmp ? m_accounts->findAccount(tmp) : 0;
}
bool dataChanged = false;
MucRoom* r = 0;
bool changed = getRoom(w,acc,m_add,m_add,r,dataChanged,false);
if (!r)
return;
if (r->local() || r->remote()) {
if (dataChanged)
Client::self()->action(w,s_storeContact + ":" + r->toString());
}
else {
s_mucRooms.clearSection(r->uri());
NamedList* sect = s_mucRooms.createSection(r->uri());
if (sect) {
sect->addParam("nick",r->m_params[YSTRING("nick")],false);
sect->addParam("password",r->m_password,false);
s_mucRooms.save();
}
}
NamedList params("");
params.addParam("force",String::boolText(changed));
if (Client::self()->action(w,s_mucJoin + ":" + r->toString(),&params))
Client::self()->setVisible(toString(),false);
}
// Retrieve the selected MUC server if not currently requesting one
bool JoinMucWizard::selectedMucServer(String* buf)
{
if (m_querySrv)
return false;
Window* w = window();
if (!w)
return false;
String tmp;
if (!buf)
buf = &tmp;
Client::self()->getText(YSTRING("muc_server"),*buf,false,w);
return !buf->null();
}
// Set/reset servers query
void JoinMucWizard::setQuerySrv(bool on, const char* domain)
{
if (!on)
m_requests.clear();
m_querySrv = on;
XDebug(ClientDriver::self(),DebugAll,"JoinMucWizard(%s) query srv is %s",
c_str(),String::boolText(on));
Window* w = window();
if (!w)
return;
NamedList p("");
const char* active = String::boolText(!m_querySrv);
p.addParam("active:muc_server",active);
p.addParam("active:muc_domain",active);
p.addParam("active:muc_query_servers",active);
p.addParam("active:mucserver_joinroom",active);
p.addParam("active:room_room",active);
p.addParam("active:mucserver_queryrooms",active);
addProgress(p,m_querySrv,domain);
if (isCurrentPage(YSTRING("pageMucServer")))
updateActions(p,!m_querySrv,selectedMucServer(),m_querySrv);
Client::self()->setParams(&p,w);
}
// Set/reset rooms query
void JoinMucWizard::setQueryRooms(bool on, const char* domain)
{
if (!isCurrentPage(YSTRING("pageRooms")))
return;
Window* w = window();
if (!w)
return;
m_queryRooms = on;
XDebug(ClientDriver::self(),DebugAll,"JoinMucWizard(%s) query rooms is %s",
c_str(),String::boolText(on));
NamedList p("");
p.addParam("active:muc_rooms",String::boolText(!m_queryRooms));
addProgress(p,m_queryRooms,domain);
String sel;
if (!m_queryRooms)
Client::self()->getSelect(YSTRING("muc_rooms"),sel,w);
updateActions(p,!m_queryRooms,!sel.null(),m_queryRooms);
Client::self()->setParams(&p,w);
}
// Update UI progress params
void JoinMucWizard::addProgress(NamedList& dest, bool on, const char* target)
{
dest.addParam("show:frame_progress",String::boolText(on));
if (on) {
String tmp("Waiting");
tmp.append(target," for ");
dest.addParam("progress_text",tmp + " ...");
}
}
// Update 'next' button status on select server page
void JoinMucWizard::updatePageMucServerNext()
{
Window* w = window();
if (!w)
return;
if (m_querySrv)
return;
bool on = false;
while (true) {
String tmp;
Client::self()->getText(YSTRING("muc_server"),tmp,false,w);
if (!tmp)
break;
bool join = false;
Client::self()->getCheck(YSTRING("mucserver_joinroom"),join,w);
if (join) {
tmp.clear();
Client::self()->getText(YSTRING("room_room"),tmp,false,w);
if (!tmp)
break;
}
on = true;
break;
}
Client::self()->setActive(s_actionNext,on,w);
}
/*
* AccountStatus
*/
// Change the current item. Save to config if changed
bool AccountStatus::setCurrent(const String& name)
{
AccountStatus* s = find(name);
if (!s)
return false;
s_current = s;
updateUi();
Client::s_settings.setValue("accountstatus","default",s_current->toString());
Client::s_settings.save();
return true;
}
// Append/set an item. Save to config if changed
void AccountStatus::set(const String& name, int stat, const String& text, bool save)
{
if (stat == ClientResource::Unknown || stat == ClientResource::Connecting)
return;
AccountStatus* item = find(name);
if (!item) {
item = new AccountStatus(name);
s_items.append(item);
}
bool changed = item->m_status != stat || item->m_text != text;
if (!changed)
return;
item->m_status = stat;
item->m_text = text;
if (!save)
return;
String s = lookup(item->status(),ClientResource::s_statusName);
s << "," << item->text();
Client::s_settings.setValue("accountstatus",item->toString(),s);
Client::s_settings.save();
}
// Load the list from config
void AccountStatus::load()
{
static bool loaded = false;
if (loaded)
return;
NamedList* as = Client::s_settings.getSection("accountstatus");
if (!as)
return;
// Load the list from config
loaded = true;
unsigned int n = as->length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = as->getParam(i);
if (!(ns && ns->name()) || ns->name() == "default")
continue;
int stat = ClientResource::Unknown;
String text;
int pos = ns->find(',');
if (pos > 0) {
stat = lookup(ns->substr(0,pos),ClientResource::s_statusName,stat);
text = ns->substr(pos + 1);
}
else
stat = lookup(*ns,ClientResource::s_statusName,stat);
set(ns->name(),stat,text);
}
setCurrent((*as)["default"]);
}
// Initialize the list
void AccountStatus::init()
{
if (s_items.skipNull())
return;
const TokenDict* d = ClientResource::s_statusName;
for (; d && d->token; d++)
set(d->token,d->value,String::empty());
setCurrent(lookup(ClientResource::Online,ClientResource::s_statusName));
}
// Update
void AccountStatus::updateUi()
{
if (!(s_current && Client::self()))
return;
NamedList p("");
p.addParam("image:global_account_status",resStatusImage(s_current->status()));
String info("Current status: ");
if (s_current->text())
info << s_current->text();
else
info << ClientResource::statusDisplayText(s_current->status());
p.addParam("property:global_account_status:toolTip",info);
Client::self()->setParams(&p);
}
/*
* PendingRequest
*/
// Remove all account's requests
void PendingRequest::clear(const String& account)
{
Lock lck(s_mutex);
for (ObjList* o = s_items.skipNull(); o; ) {
PendingRequest* req = static_cast<PendingRequest*>(o->get());
if (req->account() != account)
o = o->skipNext();
else {
o->remove();
o = o->skipNull();
}
}
}
// Remove all contacts's requests
void PendingRequest::cancel(ClientContact* c, const String& res)
{
if (!c)
return;
String s;
buildIdNoType(s,*c,res,String::empty(),false);
s << "_";
Lock lck(s_mutex);
for (ObjList* o = s_items.skipNull(); o; ) {
PendingRequest* req = static_cast<PendingRequest*>(o->get());
int pos = req->requestId().find("_");
if (pos <= 0 || s != req->requestId().substr(pos + 1,s.length()))
o = o->skipNext();
else {
o->remove();
o = o->skipNull();
}
}
}
// Build request id
void PendingRequest::buildIdNoType(String& buf, const String& acc, const String& target,
const String& res, const String& extra, bool addTime)
{
if (!acc)
return;
buf << acc.hash();
if (target)
buf << "_" << target.hash();
if (res)
buf << "_" << res.hash();
if (extra)
buf << "_" << extra.hash();
if (addTime)
buf << "_" << (unsigned int)Time::msecNow();
}
// Start a request, consume the objects
bool PendingRequest::start(PendingRequest* r, Message* m, u_int64_t delayUs)
{
if (!(r && m)) {
TelEngine::destruct(r);
TelEngine::destruct(m);
return false;
}
Lock lck(s_mutex);
if (!find(r->toString())) {
s_items.append(r);
if (delayUs && r->setPendingMsg(m,delayUs))
return true;
lck.drop();
Engine::enqueue(m);
}
else {
lck.drop();
TelEngine::destruct(r);
TelEngine::destruct(m);
}
return true;
}
/*
* SharedPendingRequest
*/
// Build message for request
Message* SharedPendingRequest::buildMessage()
{
Message* m = PendingRequest::buildMessageTo("file.info","query");
if (m_dir) {
m->addParam("dir",m_what);
m->addParam("rsm_index",String(m_index));
m->addParam("rsm_max",String(s_fileInfoMax));
}
else
m->addParam("file",m_what);
return m;
}
// Start a request
bool SharedPendingRequest::start(const String& account, const String& contact,
const String& inst, const String& what, bool dir, unsigned int index,
u_int64_t delayUs)
{
String s;
buildId(s,SharedQuery,account,contact,inst,what);
SharedPendingRequest* r = new SharedPendingRequest(s,account,contact,inst);
r->m_dir = dir;
r->m_what = what;
r->m_index = index;
Message* m = r->buildMessage();
return PendingRequest::start(r,m,delayUs);
}
/*
* ContactChatNotify
*/
// Check for timeout. Reset the timer if a notification is returned
ContactChatNotify::State ContactChatNotify::timeout(Time& time)
{
if (m_paused) {
if (m_paused > time.msec())
return None;
m_paused = 0;
return Paused;
}
if (m_inactive) {
if (m_inactive > time.msec())
return None;
m_inactive = 0;
return Inactive;
}
return None;
}
// Send the notification
void ContactChatNotify::send(State state, ClientContact* c, MucRoom* room,
MucRoomMember* member)
{
const char* s = ::lookup(state,s_states);
if (!s)
return;
if (c)
c->sendChat(0,String::empty(),String::empty(),s);
else if (room)
room->sendChat(0,member ? member->m_name : String::empty(),String::empty(),s);
}
// Add or remove items from list. Notify active/composing if changed
void ContactChatNotify::update(ClientContact* c, MucRoom* room, MucRoomMember* member,
bool empty, bool notify)
{
if (!(c || room))
return;
const String& id = c ? c->toString() : (member ? member->toString() : room->toString());
if (!id)
return;
ObjList* found = s_items.find(id);
State st = Composing;
if (empty) {
if (!found)
return;
found->remove();
st = Active;
}
else {
Time time;
if (found) {
ContactChatNotify* item = static_cast<ContactChatNotify*>(found->get());
// Send composing if sent any other notification
notify = !(item->m_paused && item->m_inactive);
item->updateTimers(time);
}
else {
s_items.append(new ContactChatNotify(id,room != 0,member != 0,time));
notify = true;
}
// Make sure the logic is receiving timer notifications
Client::setLogicsTick();
}
if (notify)
send(st,c,room,member);
}
// Check timeouts. Send notifications
bool ContactChatNotify::checkTimeouts(ClientAccountList& list, Time& time)
{
ObjList* o = s_items.skipNull();
while (o) {
ContactChatNotify* item = static_cast<ContactChatNotify*>(o->get());
State state = item->timeout(time);
if (state != None) {
// Send notification
// Remove the item if there is no chat for it
ClientContact* c = 0;
MucRoom* room = 0;
MucRoomMember* member = 0;
if (!item->m_mucRoom) {
c = list.findContact(item->toString());
if (c && !c->hasChat())
c = 0;
}
else if (item->m_mucMember) {
room = list.findRoomByMember(item->toString());
if (room) {
member = room->findMemberById(item->toString());
if (!member)
room = 0;
}
if (room && !room->hasChat(member->toString()))
room = 0;
}
else {
room = list.findRoom(item->toString());
if (room && !room->hasChat(room->toString()))
room = 0;
}
if (c || room)
item->send(state,c,room,member);
else {
// Not found: remove from list
o->remove();
o = o->skipNull();
continue;
}
}
o = o->skipNext();
}
return 0 != s_items.skipNull();
}
//
// FtJob
//
void FtJob::drop()
{
if (!(m_notifyId || m_dropId))
return;
FtManager::dropFileTransferItem(m_notifyId,&m_dropId,false);
m_notifyId.clear();
m_dropId.clear();
}
void FtJob::destruct()
{
drop();
String::destruct();
}
// Drop a list of jobs. Reset job's notify id and add it to upd if non 0
unsigned int FtJob::dropJobs(ObjList& jobs, int newState, NamedList* upd)
{
unsigned int n = 0;
for (ObjList* o = jobs.skipNull(); o; o = o->skipNext()) {
FtJob* job = static_cast<FtJob*>(o->get());
if (job->m_state == Running)
n++;
if (upd && job->m_notifyId) {
upd->addParam(job->m_notifyId,"");
job->m_notifyId.clear();
}
job->drop();
job->m_state = newState;
}
return n;
}
//
// FtDownloadDirContentJob
//
void FtDownloadDirContentJob::drop()
{
FtJob::drop();
m_dir.updated(false);
m_dir.children().clear();
}
//
// FtItem
//
FtItem::FtItem(FtManager* owner, const String& itemId, const String& acc,
const String& cUri, const String& inst)
: Mutex(false,"FtItem"),
m_id(itemId), m_owner(owner), m_online(false), m_account(acc), m_contactUri(cUri),
m_instance(inst)
{
m_dbg << "account=" << acc << " contact=" << cUri << " instance=" << inst;
m_contactName = cUri;
if (m_contactName && inst)
m_contactName << "/" << inst;
}
// Set contact/instance online
bool FtItem::setOnline(bool online)
{
if (m_online == online)
return false;
m_online = online;
return true;
}
void FtItem::destroyed()
{
cancel();
if (m_refreshWnd && m_refreshName && Client::valid()) {
Window* w = Client::self()->getWindow(m_refreshWnd);
if (w)
Client::self()->setProperty(m_refreshName,"_yate_refresh",String::empty(),w);
}
RefObject::destroyed();
}
//
// DownloadBatch
//
DownloadBatch::DownloadBatch(FtManager* owner, const String& itemId, const String& acc,
const String& cUri, const String& inst)
: FtItem(owner,itemId,acc,cUri,inst),
m_dirContentReqCount(0), m_dirContentReqMax(5),
m_timeout(0),
m_timeToDownload(0), m_donwloadIntervalMs(3000)
{
DDebug(m_owner,DebugAll,"DownloadBatch %s created [%p]",m_dbg.c_str(),this);
}
// Add a shared item
void DownloadBatch::addItem(ClientFileItem& item, const String& path,
const String& itemPath, const String& refreshWnd, const String& refreshName)
{
if (!item.name())
return;
ClientDir* dir = item.directory();
ClientFile* file = item.file();
if (!(dir || file))
return;
String p;
Client::addPathSep(p,path);
p << item.name();
Client::fixPathSep(p);
lock();
bool checkOnline = !m_online;
if (dir)
addDirUnsafe(*dir,p,itemPath);
else
addFileUnsafe(p,itemPath,file->params());
m_refreshWnd = refreshWnd;
m_refreshName = refreshName;
unlock();
if (!(checkOnline && m_owner->accounts()))
return;
bool ok = false;
bool online = false;
ClientAccount* acc = 0;
ClientContact* c = 0;
while (true) {
acc = m_owner->accounts()->findAccount(m_account,true);
if (!acc)
break;
if (!acc->resource().online()) {
ok = true;
break;
}
if (!m_contactUri)
break;
c = acc->findContactByUri(m_contactUri,true);
if (!c)
break;
ok = true;
Lock lck(acc);
if (m_instance)
online = (0 != c->findResource(m_instance));
else
online = c->online();
break;
}
TelEngine::destruct(acc);
TelEngine::destruct(c);
if (ok)
setOnline(online);
else
cancel();
}
// Timer tick handler
bool DownloadBatch::timerTick(const Time& time)
{
Lock lck(this);
if (!m_online) {
if (!haveJobs())
return false;
bool done = (m_timeout && m_timeout < time);
if (done) {
Debug(m_owner,DebugNote,"%s donwloads timed out",m_dbg.c_str());
Client::addToLogFormatted("%s: %s donwloads timed out",
m_owner->debugName(),m_dbg.c_str());
}
return !done;
}
NamedList newDown("");
ObjList* o = 0;
if (!m_timeToDownload || m_timeToDownload < time)
o = m_fileDownloads.skipNull();
// Check if we can start another job
while (o) {
FtJob* job = static_cast<FtJob*>(o->get());
if (job->m_state == FtJob::Running) {
o = o->skipNext();
continue;
}
FtDownloadFileJob* file = job->downloadFileJob();
if (!file) {
DDebug(DebugStub,"DonwloadBatch: unknown pending job");
o->remove();
o = o->skipNull();
continue;
}
if (!m_owner->buildDownloadId(job->m_notifyId,toString(),*file))
break;
if (startFileDownload(file,newDown)) {
if (m_donwloadIntervalMs)
m_timeToDownload = Time::now() + m_donwloadIntervalMs * 1000;
break;
}
o->remove(false);
o = o->skipNull();
}
// Directory content request
if (m_dirContentReqCount < m_dirContentReqMax && m_owner->accounts()) {
for (o = m_retrieve.skipNull(); o;) {
FtDownloadDirContentJob* job = static_cast<FtDownloadDirContentJob*>(o->get());
if (job->m_state == FtJob::Running) {
o = o->skipNext();
continue;
}
bool ok = SharedPendingRequest::start(m_account,m_contactUri,m_instance,
job->m_downloadPath);
if (!ok) {
Debug(m_owner,DebugNote,
"%s failed to start shared directory '%s' content refresh",
m_dbg.c_str(),job->m_downloadPath.c_str());
Client::addToLogFormatted(
"%s: %s failed to start shared directory '%s' content refresh",
m_owner->debugName(),m_dbg.c_str(),job->m_downloadPath.c_str());
o->remove();
o = o->skipNull();
}
job->m_state = FtJob::Running;
m_dirContentReqCount++;
if (m_dirContentReqCount == m_dirContentReqMax)
break;
}
}
bool ok = haveJobs();
lck.drop();
if (!ok)
return false;
if (newDown)
FtManager::addFileTransferItem(newDown,true,false);
return true;
}
// Handle file transfer notifications
void DownloadBatch::handleFileTransferNotify(Message& msg, const String& notifyId)
{
if (!notifyId)
return;
Lock lck(this);
ObjList* o = findNotify(notifyId);
if (!o)
return;
FtJob* job = static_cast<FtJob*>(o->get());
FtDownloadFileJob* file = job->downloadFileJob();
const String& status = msg[YSTRING("status")];
DDebug(m_owner,DebugAll,"%s: download job '%s' status is '%s' [%p]",
m_dbg.c_str(),job->c_str(),status.c_str(),this);
if (FtManager::isRunningNotify(status)) {
lck.drop();
FtManager::updateFtProgress(notifyId,msg);
return;
}
job->m_state = FtJob::Finished;
if (file) {
const String& error = msg[YSTRING("error")];
if (!error)
Client::addToLogFormatted("%s: %s finished downloading file '%s' -> '%s'",
m_owner->debugName(),m_dbg.c_str(),file->m_file.c_str(),file->c_str());
else
Client::addToLogFormatted("%s: %s failed to download file '%s' -> '%s': %s",
m_owner->debugName(),m_dbg.c_str(),file->m_file.c_str(),file->c_str(),
error.c_str());
}
else
DDebug(DebugStub,"DonwloadBatch: can't handle termination for unknown job type");
o->remove(false);
lck.drop();
cancelJob(job,true);
}
// Handle file info responses
bool DownloadBatch::handleFileInfoRsp(const String& oper, NamedList& msg)
{
String* path = msg.getParam(YSTRING("dir"));
if (TelEngine::null(path))
return false;
bool ok = (oper == YSTRING("result"));
if (!ok && oper != YSTRING("error"))
return false;
Lock lck(this);
ObjList* o = findDirContent(*path,false);
if (!o)
return false;
ObjList items;
bool complete = false;
const char* reason = 0;
if (ok)
decodeFileInfo(msg,items,complete);
else
reason = msg.getValue(YSTRING("reason"),msg.getValue(YSTRING("error")));
// Decode received files
while (o) {
FtDownloadDirContentJob* job = static_cast<FtDownloadDirContentJob*>(o->get());
ObjList* list = 0;
if (complete || !ok) {
if (ok)
Debug(m_owner,DebugAll,"%s completed shared directory '%s' refresh",
m_dbg.c_str(),job->c_str());
else {
Debug(m_owner,DebugNote,"%s failed to refresh shared directory '%s': %s",
m_dbg.c_str(),job->c_str(),reason);
Client::addToLogFormatted("%s: %s failed to refresh shared directory '%s': %s",
m_owner->debugName(),m_dbg.c_str(),job->c_str(),reason);
}
if (m_dirContentReqCount)
m_dirContentReqCount--;
o->remove(!ok);
list = o->skipNull();
}
else
list = o->skipNext();
o = list ? findDirContent(*path,false,list) : 0;
if (!ok)
continue;
if (!o)
job->m_dir.addChildren(items);
else
job->m_dir.copyChildren(items);
if (!complete)
continue;
job->m_dir.updated(true);
addDirUnsafe(job->m_dir,*job,job->m_downloadPath);
TelEngine::destruct(job);
}
return true;
}
// Set contact/instance online
bool DownloadBatch::setOnline(bool online)
{
Lock lck(this);
if (!FtItem::setOnline(online))
return false;
if (m_online) {
m_timeout = 0;
return true;
}
// Allow a 10 minutes interval to become online
m_timeout = Time::now() + 10 * 60 * 1000000;
// Stop/reset jobs
NamedList upd("");
unsigned int n = FtJob::dropJobs(m_fileDownloads,FtJob::Pending,&upd);
FtJob::dropJobs(m_retrieve,FtJob::Pending,&upd);
lck.drop();
for (; n; n--)
m_owner->downloadTerminated();
if (upd.getParam(0))
FtManager::updateFileTransfers(upd,true);
return true;
}
// Cancel all running jobs, clear data
void DownloadBatch::cancel()
{
lock();
ObjList tmp;
moveList(tmp,m_fileDownloads);
moveList(tmp,m_retrieve);
m_fileDownloads.clear();
m_retrieve.clear();
unlock();
for (ObjList* o = tmp.skipNull(); o; o = o->skipNext()) {
FtJob* job = static_cast<FtJob*>(o->get());
o->set(0,false);
cancelJob(job,job->m_state != FtJob::Running);
}
}
void DownloadBatch::destroyed()
{
DDebug(m_owner,DebugAll,"DownloadBatch %s destroyed [%p]",m_dbg.c_str(),this);
FtItem::destroyed();
}
// Find a dir content refresh holder
ObjList* DownloadBatch::findDirContent(const String& key, bool byLocalPath,
ObjList* start) const
{
ObjList* list = start ? start : m_retrieve.skipNull();
if (!list)
return 0;
if (byLocalPath)
return list->find(key);
for (ObjList* o = list; o; o = o->skipNext()) {
FtDownloadDirContentJob* job = static_cast<FtDownloadDirContentJob*>(o->get());
if (job->m_downloadPath == key)
return o;
}
return 0;
}
// Start file download, consume the pointer
bool DownloadBatch::startFileDownload(FtDownloadFileJob* file, NamedList& uiParams)
{
if (!file)
return false;
Message m("call.execute");
m.addParam("callto","filetransfer/receive/" + *file);
if (!m_target) {
m_target << "jingle/" << m_contactUri;
m_target.append(m_instance,"/");
}
m.addParam("direct",m_target);
m.addParam("line",m_account);
m.addParam("notify_progress",String::boolText(true));
m.addParam("autoclose",String::boolText(true));
m.addParam("notify",file->m_notifyId);
m.addParam("remote_file",file->m_file);
m.addParam("create_path",String::boolText(true));
m.addParam("overwrite",String::boolText(true));
m.copyParams(file->m_file);
if (Engine::dispatch(m)) {
file->m_dropId = m[YSTRING("id")];
file->m_state = FtJob::Running;
Debug(m_owner,DebugAll,"%s start downloading file '%s' -> '%s'",
m_dbg.c_str(),file->m_file.c_str(),file->c_str());
Client::addToLogFormatted("%s: %s start downloading file '%s' -> '%s'",
m_owner->debugName(),m_dbg.c_str(),file->m_file.c_str(),file->c_str());
FtManager::buildFileTransferItem(uiParams,file->m_notifyId,false,m_account,
m_contactUri,m_instance,m_contactName,*file,file->m_dropId);
return true;
}
m_owner->downloadTerminated();
Debug(m_owner,DebugNote,"%s failed to start file '%s' download: %s",
m_dbg.c_str(),file->m_file.c_str(),m.getValue("error"));
Client::addToLogFormatted("%s: %s failed to start file '%s' download: %s",
m_owner->debugName(),m_dbg.c_str(),file->m_file.c_str(),m.getValue("error"));
TelEngine::destruct(file);
return false;
}
// Cancel a job, return true on success
bool DownloadBatch::cancelJob(FtJob*& job, bool finished)
{
if (!job)
return false;
FtDownloadFileJob* file = job->downloadFileJob();
if (!finished && file) {
Debug(m_owner,DebugNote,"%s download file '%s' -> '%s' cancelled",
m_dbg.c_str(),file->m_file.c_str(),file->c_str());
Client::addToLogFormatted("%s: %s download file '%s' -> '%s' cancelled",
m_owner->debugName(),m_dbg.c_str(),file->m_file.c_str(),file->c_str());
}
TelEngine::destruct(job);
if (file)
m_owner->downloadTerminated();
return true;
}
// Add a shared item
void DownloadBatch::addItemName(ClientFileItem& item, const String& path,
const String& itemPath)
{
ClientDir* dir = item.directory();
ClientFile* file = item.file();
if (!(dir || file))
return;
String p = path + Engine::pathSeparator() + item.name();
String ip = itemPath + "/" + item.name();
if (dir)
addDirUnsafe(*dir,p,ip);
else
addFileUnsafe(p,ip,file->params());
}
// Add a shared file
void DownloadBatch::addFileUnsafe(const String& localPath, const String& downloadPath,
const NamedList& params)
{
Debug(m_owner,DebugAll,"%s adding download file '%s' -> '%s' [%p]",
m_dbg.c_str(),downloadPath.c_str(),localPath.c_str(),m_owner);
// File already pending or running ?
if (m_fileDownloads.find(localPath)) {
Client::addToLogFormatted(
"%s: %s download file '%s' -> '%s' already in the list",
m_owner->debugName(),m_dbg.c_str(),downloadPath.c_str(),localPath.c_str());
return;
}
FtDownloadFileJob* job = new FtDownloadFileJob(localPath,downloadPath,params);
job->m_state = FtJob::Pending;
m_fileDownloads.append(job);
Client::addToLogFormatted("%s: %s added pending download file '%s' -> '%s'",
m_owner->debugName(),m_dbg.c_str(),job->m_file.c_str(),job->c_str());
}
// Add a shared directory
void DownloadBatch::addDirUnsafe(ClientDir& dir, const String& localPath,
const String& downloadPath)
{
if (!localPath)
return;
if (dir.updated()) {
for (ObjList* o = dir.children().skipNull(); o; o = o->skipNext()) {
ClientFileItem* item = static_cast<ClientFileItem*>(o->get());
addItemName(*item,localPath,downloadPath);
}
return;
}
if (findDirContent(localPath,true))
return;
FtDownloadDirContentJob* job = new FtDownloadDirContentJob(localPath,
downloadPath,dir.name());
job->m_state = FtJob::Pending;
m_retrieve.append(job);
Debug(m_owner,DebugAll,
"%s added pending shared directory content refresh local_path=%s download_path=%s",
m_dbg.c_str(),localPath.c_str(),downloadPath.c_str());
}
//
// FtManager
//
FtManager::FtManager(ClientAccountList* accounts, const char* name)
: String(name), Mutex(false,c_str()),
m_accounts(accounts),
m_jobId(0),
m_timer(0),
m_downloadBatchIter(m_downloadBatch),
m_downloadBatchChanged(false),
m_downloadCount(0),
m_downloadMax(10)
{
debugName(c_str());
m_downloadNotifyPrefix << name << "/";
}
FtManager::~FtManager()
{
cancel();
}
// Build a download id if possible
bool FtManager::buildDownloadId(String& buf, const String& requestorId,
const String& requestId)
{
Lock lck(this);
if (m_downloadCount >= m_downloadMax)
return false;
m_downloadCount++;
buf = m_downloadNotifyPrefix;
buf << requestorId.sqlEscape('/') << "/" << requestId << "/" << ++m_jobId;
return true;
}
// Drop a job. Return true if found
bool FtManager::cancelFileTransfer(const String& notifyId)
{
bool found = false;
if (notifyId.startsWith(m_downloadNotifyPrefix)) {
RefPointer<DownloadBatch> d;
if (findDownloadBatchNotify(d,notifyId)) {
found = d->cancel(notifyId);
d = 0;
}
}
return found;
}
// Drop jobs for account/contact
void FtManager::cancel(const String& account, const String& contact)
{
if (!account)
return;
lock();
ObjList tmpDwn;
ListIterator iter(m_downloadBatch);
for (ObjList* o = m_downloadBatch.skipNull(); o;) {
DownloadBatch* d = static_cast<DownloadBatch*>(o->get());
if (!d->match(account,contact))
o = o->skipNext();
else {
tmpDwn.append(o->remove(false));
o = o->skipNull();
}
}
unlock();
tmpDwn.clear();
}
// Cancel all jobs, stop timer tick thread
void FtManager::cancel()
{
lock();
ObjList tmpDwn;
moveList(tmpDwn,m_downloadBatch);
m_downloadBatchChanged = true;
unlock();
tmpDwn.clear();
cancelTimer();
}
bool FtManager::findDownloadBatch(RefPointer<DownloadBatch>& d, const String& acc,
const String& contact, const String& inst)
{
String cId;
String tmp;
ClientContact::buildContactId(cId,acc,contact);
ClientContact::buildContactInstanceId(tmp,cId,inst);
Lock lck(this);
d = findDownloadBatch(tmp);
return (d != 0);
}
// Find a batch download by notify id
bool FtManager::findDownloadBatchNotify(RefPointer<DownloadBatch>& d,
const String& s)
{
int pLen = m_downloadNotifyPrefix.length();
int pos = s.find('/',pLen + 1);
if (pos <= pLen)
return false;
String batchId = s.substr(pLen,pos - pLen);
Lock lck(this);
d = findDownloadBatch(batchId);
return (d != 0);
}
// Add an item to batch downloads
void FtManager::addShareDownload(const String& acc, const String& contact,
const String& inst, const String& item, const String& path,
const String& refreshWnd, const String& refreshName)
{
if (!(m_accounts && item && path))
return;
ClientContact* c = m_accounts->findContactByUri(acc,contact);
ClientDir* dir = c ? c->getShared(inst) : 0;
ClientFileItem* it = dir ? dir->findChild(item) : 0;
if (it) {
addShareDownload(*c,inst,*it,path,item,refreshWnd,refreshName);
return;
}
const char* reason = !c ? "contact not found" : "shared not found";
Client::addToLogFormatted(
"Failed to add download for '%s' account=%s contact=%s instance=%s: %s",
item.c_str(),acc.safe(),contact.safe(),inst.safe(),reason);
}
// Add an item to batch downloads
void FtManager::addShareDownload(ClientContact& c, const String& inst,
ClientFileItem& item, const String& path, const String& itemPath,
const String& refreshWnd, const String& refreshName)
{
if (!(inst && path))
return;
String tmp;
c.buildInstanceId(tmp,inst);
lock();
RefPointer<DownloadBatch> d = findDownloadBatch(tmp);
if (!d) {
d = new DownloadBatch(this,tmp,c.accountName(),c.uri(),inst);
m_downloadBatch.append(d);
m_downloadBatchChanged = true;
}
unlock();
d->addItem(item,path,itemPath,refreshWnd,refreshName);
Lock lck(this);
if (!m_downloadBatch.find(d)) {
m_downloadBatch.append(d);
m_downloadBatchChanged = true;
}
else
d = 0;
if (!m_timer) {
m_timer = new FTManagerTimer(this);
m_timer->startup();
}
}
// Timer tick terminated notification
void FtManager::timerTerminated(FTManagerTimer* timer)
{
Lock lck(this);
if (m_timer != timer)
return;
m_timer = 0;
}
// Timer tick handler
bool FtManager::timerTick(const Time& time)
{
lock();
if (m_downloadBatchChanged) {
m_downloadBatchIter.assign(m_downloadBatch);
m_downloadBatchChanged = false;
}
else
m_downloadBatchIter.reset();
DownloadBatch* delLater = 0;
for (GenObject* gen = 0; (0 != (gen = m_downloadBatchIter.get()));) {
DownloadBatch* d = static_cast<DownloadBatch*>(gen);
if (!d->ref())
continue;
unlock();
TelEngine::destruct(delLater);
bool del = !d->timerTick(time);
if (del)
delLater = d;
else
d->deref();
lock();
if (del) {
m_downloadBatch.remove(d);
m_downloadBatchChanged = true;
}
}
bool haveDownloads = (0 != m_downloadBatch.skipNull());
bool retVal = haveDownloads;
if (!retVal)
m_timer = 0;
unlock();
TelEngine::destruct(delLater);
if (!haveDownloads)
hideEmptyFtWindow();
return retVal;
}
// Handle file transfer notifications
bool FtManager::handleFileTransferNotify(Message& msg, const String& notifyId)
{
if (notifyId.startsWith(m_downloadNotifyPrefix)) {
RefPointer<DownloadBatch> d;
if (findDownloadBatchNotify(d,notifyId)) {
d->handleFileTransferNotify(msg,notifyId);
d = 0;
}
return true;
}
return false;
}
// Handle file info responses
bool FtManager::handleFileInfoRsp(const String& account, const String& contact,
const String& inst, const String& oper, NamedList& msg)
{
RefPointer<DownloadBatch> d;
if (findDownloadBatch(d,account,contact,inst)) {
bool ok = d->handleFileInfoRsp(oper,msg);
d = 0;
return ok;
}
return false;
}
// Handle resource.notify
void FtManager::handleResourceNotify(bool online, const String& account, const String& contact,
const String& inst)
{
if (!account)
return;
if (inst) {
if (!contact)
return;
RefPointer<DownloadBatch> d;
if (findDownloadBatch(d,account,contact,inst)) {
d->setOnline(online);
d = 0;
}
return;
}
// Online with no instance: nothing to be done
if (online)
return;
lock();
ListIterator iter(m_downloadBatch);
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
RefPointer<DownloadBatch> d = static_cast<DownloadBatch*>(gen);
if (!d)
continue;
if (!d->match(account,contact))
continue;
unlock();
d->setOnline(false);
d = 0;
lock();
}
unlock();
}
// Update file transfer items
bool FtManager::updateFileTransfers(NamedList& params, bool checkEmpty)
{
if (!Client::valid())
return false;
Window* w = Client::self()->getWindow(s_wndFileTransfer);
if (!w)
return false;
bool ok = Client::self()->updateTableRows(s_fileProgressList,&params,false,w);
if (ok && checkEmpty) {
NamedList items("");
Client::self()->getOptions(s_fileProgressList,&items,w);
if (items.getParam(0))
Client::self()->setSelect(s_fileProgressCont,s_pageList,w);
else {
Client::self()->setSelect(s_fileProgressCont,s_pageEmpty,w);
Client::self()->setVisible(s_wndFileTransfer,false);
}
}
return ok;
}
// addNew: true to add a new item if not found
bool FtManager::updateFileTransferItem(bool addNew, const String& id, NamedList& params,
bool setVisible, bool activate)
{
if (!Client::valid())
return false;
Window* w = Client::self()->getWindow(s_wndFileTransfer);
if (!w)
return false;
NamedList p("");
NamedPointer* np = new NamedPointer(id,&params,String::boolText(addNew));
p.addParam(np);
bool ok = Client::self()->updateTableRows(s_fileProgressList,&p,false,w);
if (ok)
Client::self()->setSelect(s_fileProgressCont,s_pageList,w);
np->takeData();
if (setVisible)
Client::self()->setVisible(s_wndFileTransfer,true,activate);
return ok;
}
// Build file transfer item update data
void FtManager::buildFileTransferItem(NamedList& list, const String& notifyId, bool send,
const String& account, const String& contact, const String& inst, const String& cName,
const String& file, const String& chan)
{
list.assign(notifyId);
String text;
text << (send ? "Sending '" : "Receiving '") << file << "'";
text.append(cName ? cName : contact," from ");
list.addParam("text",text);
list.addParam("send",String::boolText(send));
list.addParam("select:progress","0");
list.addParam("account",account,false);
list.addParam("contact",contact,false);
list.addParam("contact_name",cName,false);
list.addParam("file",file);
list.addParam("channel",chan,false);
list.addParam("instance",inst,false);
}
// Update item progress
bool FtManager::updateFtProgress(const String& notifyId, NamedList& params)
{
unsigned int trans = params.getIntValue(YSTRING("transferred"),0,0);
unsigned int total = params.getIntValue(YSTRING("total"),0,0);
String progress;
if (total && total > trans)
progress = (unsigned int)((u_int64_t)trans * 100 / total);
else
return false;
NamedList p(notifyId);
p.addParam("select:progress",progress);
return updateFileTransferItem(false,notifyId,p);
}
// Update finished item
bool FtManager::updateFtFinished(const String& notifyId, NamedList& params,
bool dropChan, const String* file, const String* contact, bool* terminated)
{
if (terminated && *terminated)
return false;
String tmp;
const String* chan = 0;
NamedList itemParams("");
if (dropChan || !(file && contact && terminated)) {
getFileTransferItem(notifyId,itemParams);
if (!terminated && itemParams.getBoolValue(YSTRING("finished")))
return false;
if (!contact) {
contact = itemParams.getParam(YSTRING("contact_name"));
if (TelEngine::null(contact))
contact = itemParams.getParam(YSTRING("contact"));
if (!contact)
contact = &tmp;
}
if (!file) {
file = itemParams.getParam(YSTRING("file"));
if (!file)
file = &tmp;
}
if (dropChan)
chan = itemParams.getParam(YSTRING("channel"));
}
String text;
const String& error = params[YSTRING("error")];
if (!TelEngine::null(chan))
ClientDriver::dropChan(*chan,error);
bool send = params.getBoolValue(YSTRING("send"));
String progress;
if (!error) {
progress = "100";
text << "Succesfully " << (send ? "sent '" : "received '");
text << *file << "'";
text << (send ? " to " : " from ") << *contact;
}
else {
text << "Failed to " << (send ? "send '" : "receive '");
text << *file << "'";
text << (send ? " to " : " from ") << *contact;
text << "\r\nError: " << error;
}
NamedList p(notifyId);
p.addParam("text",text);
p.addParam("select:progress",progress,false);
p.addParam("cancel","Close");
p.addParam("finished",String::boolText(true));
return updateFileTransferItem(false,notifyId,p);
}
// Retrieve a file transfer item
// Delete the item from list. Drop the channel
bool FtManager::getFileTransferItem(const String& id, NamedList& params, Window* w)
{
if (!Client::valid())
return false;
if (!w)
w = Client::self()->getWindow(s_wndFileTransfer);
return w && Client::self()->getTableRow(s_fileProgressList,id,&params,w);
}
// Drop a file transfer item
// Delete the item from list. Drop the channel
bool FtManager::dropFileTransferItem(const String& id, const String* chan, bool hideEmpty)
{
const char* reason = 0;
bool ok = false;
NamedList p("");
if (Client::valid()) {
Window* w = Client::self()->getWindow(s_wndFileTransfer);
if (w) {
if (!chan) {
getFileTransferItem(id,p,w);
chan = p.getParam(YSTRING("channel"));
reason = p.getBoolValue(YSTRING("send")) ? "cancelled" : "closed";
}
ok = Client::self()->delTableRow(s_fileProgressList,id,w);
// Close window if empty
if (hideEmpty)
hideEmptyFtWindow(w);
}
}
if (!TelEngine::null(chan))
ClientDriver::dropChan(*chan,reason);
return ok;
}
// Hide file transfer empty file transfer window
void FtManager::hideEmptyFtWindow(Window* w)
{
if (!w) {
if (Client::valid())
w = Client::self()->getWindow(s_wndFileTransfer);
if (!w)
return;
}
NamedList items("");
Client::self()->getOptions(s_fileProgressList,&items,w);
if (!items.getParam(0)) {
Client::self()->setSelect(s_fileProgressCont,s_pageEmpty,w);
Client::self()->setVisible(s_wndFileTransfer,false);
}
}
void FtManager::cancelTimer()
{
if (!m_timer)
return;
lock();
if (m_timer)
m_timer->cancel(false);
unlock();
unsigned int n = 1000 / Thread::idleMsec();
for (unsigned int i = 0; m_timer && i < n; i++)
Thread::idle();
Lock lck(this);
if (m_timer)
m_timer->cancel(true);
m_timer = 0;
}
//
// FTManagerTimer
//
FTManagerTimer::FTManagerTimer(FtManager* owner)
: Thread("FtManager"),
m_owner(owner)
{
}
FTManagerTimer::~FTManagerTimer()
{
notify();
}
void FTManagerTimer::run()
{
while (m_owner && m_owner->timerTick()) {
Thread::idle();
if (Thread::check(false))
break;
}
notify();
}
void FTManagerTimer::notify()
{
if (!m_owner)
return;
m_owner->timerTerminated(this);
m_owner = 0;
}
/**
* ClientLogic
*/
// Constructor
ClientLogic::ClientLogic(const char* name, int priority)
: m_durationMutex(true,"ClientLogic::duration"), m_name(name), m_prio(priority)
{
Debug(ClientDriver::self(),DebugAll,"ClientLogic(%s) [%p]",m_name.c_str(),this);
Client::addLogic(this);
}
// destructor
ClientLogic::~ClientLogic()
{
Debug(ClientDriver::self(),DebugAll,"ClientLogic(%s) destroyed [%p]",m_name.c_str(),this);
clearDurationUpdate();
Client::removeLogic(this);
}
// obtain the name of the object
const String& ClientLogic::toString() const
{
return m_name;
}
// function which interprets given parameters and takes appropiate action
bool ClientLogic::setParams(const NamedList& params)
{
bool ok = true;
unsigned int l = params.length();
for (unsigned int i = 0; i < l; i++) {
const NamedString* s = params.getParam(i);
if (s) {
String n(s->name());
if (n.startSkip("show:",false))
ok = Client::self()->setShow(n,s->toBoolean()) && ok;
else if (n.startSkip("active:",false))
ok = Client::self()->setActive(n,s->toBoolean()) && ok;
else if (n.startSkip("focus:",false))
ok = Client::self()->setFocus(n,s->toBoolean()) && ok;
else if (n.startSkip("check:",false))
ok = Client::self()->setCheck(n,s->toBoolean()) && ok;
else if (n.startSkip("select:",false))
ok = Client::self()->setSelect(n,*s) && ok;
else if (n.find(':') < 0)
ok = Client::self()->setText(n,*s) && ok;
else
ok = false;
}
}
return ok;
}
// Add a duration object to this client's list
bool ClientLogic::addDurationUpdate(DurationUpdate* duration, bool autoDelete)
{
if (!duration)
return false;
Lock lock(m_durationMutex);
m_durationUpdate.append(duration)->setDelete(autoDelete);
DDebug(ClientDriver::self(),DebugInfo,
"Logic(%s) added duration ('%s',%p) owner=%u",
m_name.c_str(),duration->toString().c_str(),duration,autoDelete);
return true;
}
// Remove a duration object from list
bool ClientLogic::removeDurationUpdate(const String& name, bool delObj)
{
if (!name)
return false;
Lock lock(m_durationMutex);
DurationUpdate* duration = findDurationUpdate(name,false);
if (!duration)
return false;
m_durationUpdate.remove(duration,false);
DDebug(ClientDriver::self(),DebugInfo,
"Logic(%s) removed duration ('%s',%p) delObj=%u",
m_name.c_str(),duration->toString().c_str(),duration,delObj);
lock.drop();
duration->setLogic(0);
if (delObj)
TelEngine::destruct(duration);
return true;
}
// Remove a duration object from list
bool ClientLogic::removeDurationUpdate(DurationUpdate* duration, bool delObj)
{
if (!duration)
return false;
Lock lock(m_durationMutex);
ObjList* obj = m_durationUpdate.find(duration);
if (!obj)
return false;
obj->remove(false);
DDebug(ClientDriver::self(),DebugInfo,
"Logic(%s) removed duration ('%s',%p) delObj=%u",
m_name.c_str(),duration->toString().c_str(),duration,delObj);
lock.drop();
duration->setLogic(0);
if (delObj)
TelEngine::destruct(duration);
return true;
}
// Find a duration update by its name
DurationUpdate* ClientLogic::findDurationUpdate(const String& name, bool ref)
{
Lock lock(m_durationMutex);
ObjList* obj = m_durationUpdate.find(name);
if (!obj)
return 0;
DurationUpdate* duration = static_cast<DurationUpdate*>(obj->get());
return (!ref || duration->ref()) ? duration : 0;
}
// Remove all duration objects
void ClientLogic::clearDurationUpdate()
{
Lock lock(m_durationMutex);
// Reset logic pointer: some of them may not be destroyed when clearing the list
ListIterator iter(m_durationUpdate);
for (GenObject* o = 0; 0 != (o = iter.get());)
(static_cast<DurationUpdate*>(o))->setLogic();
m_durationUpdate.clear();
}
// Release memory. Remove from client's list
void ClientLogic::destruct()
{
clearDurationUpdate();
Client::removeLogic(this);
GenObject::destruct();
}
// Init static logic data
void ClientLogic::initStaticData()
{
// Build account status
AccountStatus::init();
// Build account options list
if (!s_accOptions.skipNull()) {
s_accOptions.append(new String("allowplainauth"));
s_accOptions.append(new String("noautorestart"));
s_accOptions.append(new String("oldstyleauth"));
s_accOptions.append(new String("tlsrequired"));
}
// Build protocol list
s_protocolsMutex.lock();
if (!s_protocols.skipNull()) {
s_protocols.append(new String("sip"));
s_protocols.append(new String("jabber"));
s_protocols.append(new String("h323"));
s_protocols.append(new String("iax"));
}
s_protocolsMutex.unlock();
}
// Save a contact into a configuration file.
bool ClientLogic::saveContact(Configuration& cfg, ClientContact* c, bool save)
{
if (!c)
return false;
String sectName(c->uri());
sectName.toLower();
NamedList* sect = cfg.createSection(sectName);
MucRoom* room = c->mucRoom();
if (room) {
sect->setParam("type","groupchat");
sect->setParam("name",room->m_name);
sect->setParam("password",room->m_password);
}
else
sect->setParam("type","chat");
sect->copyParams(c->m_params);
sect->clearParam(YSTRING("group"));
for (ObjList* o = c->groups().skipNull(); o; o = o->skipNext())
sect->addParam("group",o->get()->toString(),false);
sect->clearParam(YSTRING("internal"),'.');
return !save || cfg.save();
}
// Delete a contact from a configuration file
bool ClientLogic::clearContact(Configuration& cfg, ClientContact* c, bool save)
{
if (!c)
return false;
String sectName(c->uri());
cfg.clearSection(sectName.toLower());
return !save || cfg.save();
}
// Called when the user selected a line
bool ClientLogic::line(const String& name, Window* wnd)
{
int l = name.toInteger(-1);
if (l >= 0 && Client::self()) {
Client::self()->line(l);
return true;
}
return false;
}
// Show/hide widget(s)
bool ClientLogic::display(NamedList& params, bool widget, Window* wnd)
{
if (!Client::self())
return false;
bool result = false;
unsigned int n = params.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* p = params.getParam(i);
if (!p)
continue;
bool tmp = false;
if (widget)
tmp = Client::self()->setShow(p->name(),p->toBoolean(),wnd);
else
tmp = Client::self()->setVisible(p->name(),p->toBoolean(),true);
if (tmp)
params.clearParam(p->name());
else
result = false;
}
return result;
}
// Called when the user pressed the backspace key.
// Erase the last digit from the given item and set focus on it
bool ClientLogic::backspace(const String& name, Window* wnd)
{
if (!Client::self())
return false;
String str;
if (Client::self()->getText(name,str,false,wnd) &&
(!str || Client::self()->setText(name,str.substr(0,str.length()-1),false,wnd)))
Client::self()->setFocus(name,false,wnd);
return true;
}
// Called when the user pressed a command action
bool ClientLogic::command(const String& name, Window* wnd)
{
Message* m = new Message("engine.command");
m->addParam("line",name);
Engine::enqueue(m);
return true;
}
// Called when the user changes debug options
bool ClientLogic::debug(const String& name, bool active, Window* wnd)
{
// pos: module name
int pos = name.find(':');
if (pos <= 0)
return false;
// posLine: active/inactive command line
int posLine = name.find(':',pos + 1);
if (posLine < 0 || posLine - pos < 2)
return false;
// Get module/line and enqueue the message
ObjList* modules = name.substr(0,pos).split(',',false);
String line = (active ? name.substr(pos + 1,posLine - pos - 1) : name.substr(posLine + 1));
for (ObjList* o = modules->skipNull(); o; o = o->skipNext()) {
Message* m = new Message("engine.debug");
m->addParam("module",o->get()->toString());
m->addParam("line",line);
Engine::enqueue(m);
}
TelEngine::destruct(modules);
return true;
}
/**
* DefaultLogic
*/
// Constructor
DefaultLogic::DefaultLogic(const char* name, int prio)
: ClientLogic(name,prio),
m_accounts(0),
m_ftManager(0)
{
m_accounts = new ClientAccountList(name,new ClientAccount(NamedList::empty()));
s_accWizard = new AccountWizard(m_accounts);
s_mucWizard = new JoinMucWizard(m_accounts);
m_ftManager = new FtManager(m_accounts,"FileTransferManager");
// Init chat states
s_chatStates.addParam("composing","${sender} is typing ...");
s_chatStates.addParam("paused","${sender} stopped typing");
s_chatStates.addParam("gone","${sender} ended chat session");
s_chatStates.addParam("inactive","${sender} is idle");
s_chatStates.addParam("active","");
// Account select params default value
s_accProtoParamsSel.addParam("ip_transport","UDP");
}
// Destructor
DefaultLogic::~DefaultLogic()
{
TelEngine::destruct(s_accWizard);
TelEngine::destruct(s_mucWizard);
TelEngine::destruct(m_ftManager);
TelEngine::destruct(m_accounts);
}
// main function which considering de value of the "action" parameter
// Handle actions from user interface
bool DefaultLogic::action(Window* wnd, const String& name, NamedList* params)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) action '%s' in window (%p,%s)",
toString().c_str(),name.c_str(),wnd,wnd ? wnd->id().c_str() : "");
// Translate actions from confirmation boxes
// the window context specifies what action will be taken forward
if (wnd && wnd->context() && (name == "ok") && (wnd->context() != "ok")) {
bool ok = action(wnd,wnd->context(),params);
if (ok)
wnd->hide();
return ok;
}
// Show/hide widgets/windows
bool widget = (name == YSTRING("display"));
if (widget || name == YSTRING("show"))
return params ? display(*params,widget,wnd) : false;
// Start a call
if (name == s_actionCall || name == YSTRING("callto")) {
NamedList dummy("");
if (!params)
params = &dummy;
return callStart(*params,wnd,name);
}
// Start a call from an action specifying the target
if (name.startsWith("callto:")) {
NamedList dummy("");
if (!params)
params = &dummy;
params->setParam("target",name.substr(7));
return callStart(*params,wnd);
}
// Answer/Hangup
bool anm = (name == s_actionAnswer);
if (anm || name == s_actionHangup) {
if (!m_selectedChannel)
return false;
if (anm)
Client::self()->callAnswer(m_selectedChannel);
else
Client::self()->callTerminate(m_selectedChannel);
return true;
}
anm = name.startsWith("answer:");
if ((anm || name.startsWith("hangup:")) && name.at(7)) {
if (anm)
Client::self()->callAnswer(name.substr(7));
else
Client::self()->callTerminate(name.substr(7));
return true;
}
bool callDrop = name.startsWith("calldrop:");
if (callDrop || name.startsWith("calldroppeer:")) {
int ppos = callDrop ? 9 : 13;
int pos = name.find(':',ppos + 1);
if (pos < 0)
ClientDriver::dropChan(name.substr(ppos),0,!callDrop);
else
ClientDriver::dropChan(name.substr(ppos,pos - ppos),name.substr(pos),!callDrop);
return true;
}
// Hold
if (name.startsWith("hold:")) {
if (!ClientDriver::self())
return false;
String chanId = name.substr(5);
if (chanId) {
ClientChannel* chan = ClientDriver::findActiveChan();
if (chan && chan->id() == chanId)
ClientDriver::self()->setActive();
else
ClientDriver::self()->setActive(chanId);
TelEngine::destruct(chan);
}
return true;
}
// Conference
if (name.startsWith("conf_add:"))
return handleChanItemConfTransfer(true,name.substr(9),wnd);
// Transfer
if (name.startsWith("transfer_start:"))
return handleChanItemConfTransfer(false,name.substr(15),wnd);
// Double click on channel: set the active call
if (name == s_channelList)
return m_selectedChannel && ClientDriver::self() &&
ClientDriver::self()->setActive(m_selectedChannel);
// Digit(s) pressed
if (name.startsWith("digit:")) {
NamedList dummy("");
if (!params)
params = &dummy;
params->setParam("digits",name.substr(6));
return digitPressed(*params,wnd);
}
// New line
if (name.startsWith("line:") && line(name.substr(5),wnd))
return false;
// Action taken when receiving a clear action
if (name.startsWith("clear:") && name.at(6))
return clearList(name.substr(6),wnd);
// Delete a list/table item
bool confirm = name.startsWith("deleteitemconfirm:");
if (confirm || name.startsWith("deleteitem:")) {
String list;
int start = confirm ? 18 : 11;
int pos = name.find(":",start);
if (pos > 0)
return deleteItem(name.substr(start,pos - start),name.substr(pos + 1),wnd,confirm);
return false;
}
// Delete a selected list/table item
if (name.startsWith("deleteselecteditem:") && name.at(19))
return deleteSelectedItem(name.substr(19),wnd);
if (name.startsWith("deletecheckeditems:") && name.at(19))
return deleteSelectedItem(name.substr(19),wnd,true);
// 'settext' action
if (name.startsWith("settext:") && name.at(8)) {
int pos = name.find(':',9);
String ctrl;
String text;
if (pos > 9) {
ctrl = name.substr(8,pos - 8);
text = name.substr(pos + 1);
}
else
ctrl = name.substr(8);
bool ok = Client::self() && Client::self()->setText(ctrl,text,false,wnd);
if (ok)
Client::self()->setFocus(ctrl,false,wnd);
return ok;
}
// action taken when receiving a backspace
if (name.startsWith("back:"))
return backspace(name.substr(5),wnd);
if (name.startsWith("command:") && name.at(8))
return command(name.substr(8),wnd);
// *** Specific action handlers
if (handleChatContactAction(name,wnd) ||
handleMucsAction(name,wnd,params) ||
handleChatContactEditOk(name,wnd) ||
handleChatRoomEditOk(name,wnd) ||
handleFileTransferAction(name,wnd,params) ||
handleFileShareAction(wnd,name,params))
return true;
// *** MUC
if (name == YSTRING("joinmuc_wizard")) {
s_mucWizard->start();
return true;
}
// *** Account management
// Create a new account or edit an existing one
bool newAcc = (name == YSTRING("acc_new"));
if (newAcc || name == YSTRING("acc_edit") || name == s_accountList)
return editAccount(newAcc,params,wnd);
if (name == YSTRING("acc_new_wizard")) {
s_accWizard->start();
return true;
}
// User pressed ok button in account edit window
if (name == YSTRING("acc_accept"))
return acceptAccount(params,wnd);
// Delete an account
if (name.startsWith("acc_del")) {
// Empty: delete the current selection
if (!name.at(7))
return delAccount(String::empty(),wnd);
// Handle 'acc_del:'
if (name.length() > 9 && name.at(7) == ':' && name.at(8))
return delAccount(name.substr(8),wnd);
}
// Login/logout
bool login = (name == s_actionLogin);
if (login || name == s_actionLogout) {
ClientAccount* acc = selectedAccount(*m_accounts,wnd);
return acc ? ::loginAccount(this,acc->params(),login) : false;
}
login = name.startsWith(s_actionLogin + ":",false);
if (login || name.startsWith(s_actionLogout + ":",false)) {
ClientAccount* acc = 0;
if (login)
acc = m_accounts->findAccount(name.substr(s_actionLogin.length() + 1));
else
acc = m_accounts->findAccount(name.substr(s_actionLogout.length() + 1));
return acc ? ::loginAccount(this,acc->params(),login) : false;
}
// Account status
if (name.startsWith("setStatus")) {
if (AccountStatus::setCurrent(name.substr(9).toLower()))
setAccountsStatus(m_accounts);
return true;
}
// *** Address book actions
// Call the current contact selection
if (name == YSTRING("abk_call") || name == s_contactList)
return callContact(params,wnd);
// Add/edit contact
bool newCont = (name == YSTRING("abk_new"));
if (newCont || name == YSTRING("abk_edit"))
return editContact(newCont,params,wnd);
// Delete a contact
if (name.startsWith("abk_del")) {
// Empty: delete the current selection
if (!name.at(7))
return delContact(String::empty(),wnd);
// Handle 'abk_del:'
if (name.length() > 9 && name.at(7) == ':' && name.at(8))
return delContact(name.substr(8),wnd);
}
// User pressed "ok" in a pop-up window like the one
// for adding/editing a contact
if (name == YSTRING("abk_accept"))
return acceptContact(params,wnd);
// *** Call log management
bool logCall = (name == YSTRING("log_call"));
if (logCall || name == YSTRING("log_contact")) {
String billid;
if (Client::valid())
Client::self()->getSelect(s_logList,billid,wnd);
if (!billid)
return false;
if (logCall)
return callLogCall(billid,wnd);
return callLogCreateContact(billid);
}
if (name == YSTRING("log_clear"))
return callLogClear(s_logList,String::empty());
// *** Miscellaneous
// List item changed
if (name == YSTRING("listitemchanged")) {
if (!params)
return false;
const String& list = (*params)[YSTRING("widget")];
if (!list)
return false;
const String& item = (*params)[YSTRING("item")];
if (!item)
return false;
return handleListItemChanged(wnd,list,item,*params);
}
// Drag&Drop actions
bool dropAsk = name == YSTRING("_yate_event_drop_accept");
if (dropAsk || name == YSTRING("_yate_event_drop")) {
if (!params)
return false;
const String& ctrl = (*params)[YSTRING("widget")];
return ctrl && handleDrop(dropAsk,wnd,ctrl,*params);
}
// OK actions
if (name == YSTRING("ok")) {
if (wnd && wnd->id() == s_wndMucInvite)
return handleMucInviteOk(wnd);
}
// Handle show window actions
if (name.startsWith("action_show_"))
Client::self()->setVisible(name.substr(12),true,true);
if (name.startsWith("action_toggleshow_")) {
String wnd = name.substr(18);
return wnd && Client::self() &&
Client::self()->setVisible(wnd,!Client::self()->getVisible(wnd),true);
}
// Help commands
if (name.startsWith("help:"))
return help(name,wnd);
// Hide windows
if (name == YSTRING("button_hide") && wnd)
return Client::self() && Client::self()->setVisible(wnd->toString(),false);
// Show/hide messages
bool showMsgs = (name == YSTRING("messages_show") || name == s_actionShowNotification ||
name == s_actionShowInfo);
if (showMsgs || name == YSTRING("messages_close")) {
bool notif = (name == s_actionShowNotification);
if (notif || name == s_actionShowInfo) {
removeTrayIcon(notif ? YSTRING("notification") : YSTRING("info"));
if (wnd && Client::valid())
Client::self()->setVisible(wnd->id(),true,true);
}
return showNotificationArea(showMsgs,wnd);
}
// Dialog actions
// Return 'true' to close the dialog
bool dlgRet = false;
if (handleDialogAction(name,dlgRet,wnd))
return dlgRet;
// Wizard actions
if (s_accWizard->action(wnd,name,params) ||
s_mucWizard->action(wnd,name,params))
return true;
ClientWizard* wiz = findTempWizard(wnd);
if (wiz && wiz->action(wnd,name,params))
return true;
// Actions from notification area
if (handleNotificationAreaAction(name,wnd))
return true;
if (name == YSTRING("textchanged"))
return handleTextChanged(params,wnd);
// Input password/credentials
bool inputPwd = name.startsWith("loginpassword:");
if (inputPwd || name.startsWith("logincredentials:"))
return handleAccCredInput(wnd,name.substr(inputPwd ? 14 : 17),inputPwd);
if (name == s_actionShowCallsList) {
if (Client::valid()) {
Client::self()->ringer(true,false);
Client::self()->setVisible(YSTRING("mainwindow"),true,true);
activatePageCalls();
removeTrayIcon(YSTRING("incomingcall"));
}
return true;
}
if (name == s_actionPendingChat) {
showPendingChat(m_accounts);
return true;
}
// Quit
if (name == YSTRING("quit")) {
if (!Client::valid())
return false;
Client::self()->quit();
return true;
}
return false;
}
// Handle actions from checkable widgets
bool DefaultLogic::toggle(Window* wnd, const String& name, bool active)
{
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) toggle '%s'=%s in window (%p,%s)",
toString().c_str(),name.c_str(),String::boolText(active),
wnd,wnd ? wnd->id().c_str() : "");
// Check for window params
if (Client::self() && Window::isValidParamPrefix(name)) {
NamedList p("");
p.addParam(name,String::boolText(active));
return Client::self()->setParams(&p,wnd);
}
if (name.startsWith("setparams:") && name.at(10) && Client::self()) {
String tmp = name.substr(10);
ObjList* obj = tmp.split(';',false);
NamedList p("");
for (ObjList* o = obj->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
const char* param = s->c_str();
bool value = active;
if (s->at(0) == '!') {
param++;
value = !active;
}
if (*param)
p.addParam(param,String::boolText(value));
}
TelEngine::destruct(obj);
return Client::self()->setParams(&p);
}
// *** Channel actions
// Hold
if (name == s_actionHold) {
if (!ClientDriver::self())
return false;
bool ok = !active ? ClientDriver::self()->setActive() :
m_selectedChannel && ClientDriver::self()->setActive(m_selectedChannel);
if (!ok)
enableCallActions(m_selectedChannel);
return ok;
}
if (name.startsWith("hold:")) {
if (!ClientDriver::self())
return false;
String chanId = name.substr(5);
if (!chanId)
return false;
if (active)
ClientDriver::self()->setActive(chanId);
else {
ClientChannel* chan = ClientDriver::findActiveChan();
if (chan && chan->id() == chanId)
ClientDriver::self()->setActive();
TelEngine::destruct(chan);
}
return true;
}
// Transfer
if (name == s_actionTransfer) {
// Active: set init flag and wait to select the target
// Else: reset transfer on currently selected channel
if (active)
m_transferInitiated = m_selectedChannel;
else if (m_selectedChannel)
ClientDriver::setAudioTransfer(m_selectedChannel);
return true;
}
if (name.startsWith("transfer_show:"))
return handleChanShowExtra(wnd,active,name.substr(14),false);
// Conference
if (name == s_actionConf) {
bool ok = ClientDriver::setConference(m_selectedChannel,active);
if (!ok)
enableCallActions(m_selectedChannel);
return ok;
}
if (name.startsWith("conference_show:"))
return handleChanShowExtra(wnd,active,name.substr(16),true);
// Show/hide windows
if (name.startsWith("showwindow:") && name.at(11)) {
String what = name.substr(11);
if (what.startsWith("help:")) {
if (active)
return help(what,wnd);
else
return Client::valid() && Client::self()->setVisible("help",false);
}
return Client::valid() && Client::self()->setVisible(what,active,true);
}
// Wizard toggle
if (s_accWizard->toggle(wnd,name,active) ||
s_mucWizard->toggle(wnd,name,active))
return true;
// Visibility: update checkable widgets having the same name as the window
if (wnd && name == YSTRING("window_visible_changed")) {
if (!Client::valid())
return false;
const char* yText = String::boolText(active);
const char* nText = String::boolText(!active);
NamedList p("");
p.addParam("check:toggle_show_" + wnd->toString(),yText);
p.addParam("check:action_show_" + wnd->toString(),yText);
if (wnd->id() == s_wndAccount || s_accWizard->isWindow(wnd)) {
p.addParam("active:acc_new",nText);
p.addParam("active:acc_new_wizard",nText);
if (active)
fillAccEditActive(p,false);
else
fillAccEditActive(p,0 != selectedAccount(*m_accounts));
// Enable/disable account edit in notification area
NamedList params("messages");
NamedList* p = new NamedList("");
p->addParam("active:messages_acc_edit",String::boolText(!active));
params.addParam(new NamedPointer("applyall",p));
Client::self()->setParams(&params);
}
else if (wnd->id() == s_wndAddrbook) {
p.addParam("active:abk_new",nText);
fillContactEditActive(p,!active,0,false);
fillLogContactActive(p,!active);
}
else if (s_mucWizard->isWindow(wnd)) {
p.addParam("active:joinmuc_wizard",nText);
p.addParam("active:" + s_chatRoomNew,nText);
}
else if (wnd->id() == ClientContact::s_mucsWnd) {
// Hidden: destroy/close all MUCS, close log sessions
if (!active) {
// Remove from pending chat
NamedList p("");
Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,wnd);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (ns && ns->name())
removePendingChat(ns->name());
}
ObjList* o = m_accounts->accounts().skipNull();
for (; o; o = o->skipNext()) {
ClientAccount* acc = static_cast<ClientAccount*>(o->get());
ListIterator iter(acc->mucs());
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
MucRoom* room = static_cast<MucRoom*>(gen);
logCloseMucSessions(room);
if (room->local() || room->remote())
clearRoom(room);
else
TelEngine::destruct(room);
}
if (acc->resource().online())
updateChatRoomsContactList(true,acc);
}
}
}
else if (wnd->id() == ClientContact::s_dockedChatWnd) {
// Clear chat pages when hidden
// Close chat sessions
if (!active) {
if (!s_changingDockedChat) {
NamedList p("");
Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,wnd);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (ns && ns->name()) {
removePendingChat(ns->name());
logCloseSession(m_accounts->findContact(ns->name()));
}
}
}
Client::self()->clearTable(ClientContact::s_dockedChatWidget,wnd);
}
}
else if (wnd->id().startsWith(ClientContact::s_chatPrefix)) {
// Close chat session if not active and not destroyed due
// to docked chat changes
if (!(active || s_changingDockedChat))
logCloseSession(m_accounts->findContact(wnd->context()));
}
else {
ClientWizard* wiz = !active ? findTempWizard(wnd) : 0;
if (wiz)
s_tempWizards.remove(wnd->id());
}
Client::self()->setParams(&p);
return true;
}
// Window active changed
if (wnd && name == YSTRING("window_active_changed")) {
if (active) {
// Remove contact from pending when activated
if (wnd->id() == ClientContact::s_dockedChatWnd) {
String sel;
if (Client::self()->getSelect(ClientContact::s_dockedChatWidget,sel,wnd))
removePendingChat(sel,m_accounts);
}
else if (wnd->id().startsWith(ClientContact::s_chatPrefix))
removePendingChat(wnd->context());
}
return true;
}
// Select item if active. Return true if inactive
if (name.startsWith("selectitem:")) {
if (!active)
return true;
String tmp = name.substr(11);
if (!tmp)
return true;
int pos = tmp.find(':');
if (pos > 0 && Client::self())
return Client::self()->setSelect(tmp.substr(0,pos),tmp.substr(pos + 1),wnd);
return true;
}
// Set debug to window
if (name == YSTRING("log_events_debug")) {
bool ok = Client::self() && Client::self()->debugHook(active);
if (ok && !active) {
NamedList p("");
p.addParam("check:debug_sniffer",String::boolText(false));
p.addParam("check:debug_jingle",String::boolText(false));
p.addParam("check:debug_sip",String::boolText(false));
p.addParam("check:debug_h323",String::boolText(false));
p.addParam("check:debug_iax",String::boolText(false));
Client::self()->setParams(&p,wnd);
}
return ok;
}
// Enable the showing of debug information for a certain module or disable it
if (name.startsWith("debug:")) {
if (debug(name.substr(6),active,wnd))
return true;
}
// Save client settings
Client::ClientToggle clientOpt = Client::getBoolOpt(name);
if (clientOpt != Client::OptCount) {
setClientParam(name,String::boolText(active),true,false);
return true;
}
// Advanced button from account window
if (name == YSTRING("acc_showadvanced")) {
if (!Client::valid())
return false;
// Select the page(s)
String proto;
if (active) {
bool wiz = s_accWizard->isWindow(wnd);
Client::self()->getSelect(wiz ? s_accWizProtocol : s_accProtocol,proto);
}
toggle(wnd,"selectitem:acc_proto_advanced:acc_proto_advanced_" + getProtoPage(proto),true);
// Keep all in sync
Client::self()->setCheck(name,active);
// Save it
Client::s_settings.setValue("client",name,String::boolText(active));
Client::save(Client::s_settings);
return true;
}
if (name == YSTRING("advanced_mode")) {
setAdvancedMode(&active);
Client::s_settings.setValue(YSTRING("client"),name,String::boolText(active));
Client::save(Client::s_settings);
return true;
}
// Commands
if (name.startsWith("command:") && name.at(8))
return command(name.substr(8) + (active ? " on" : " off"),wnd);
// Handle show window actions
if (name.startsWith("action_show_"))
Client::self()->setVisible(name.substr(12),active,true);
// Chat log options
if (active) {
int v = lookup(name,s_chatLogDict);
if (v == ChatLogSaveAll || v == ChatLogSaveUntilLogout || v == ChatLogNoSave) {
s_chatLog = (ChatLogEnum)v;
Client::s_settings.setValue(YSTRING("client"),"logchat",name);
Client::s_settings.save();
}
}
return false;
}
// Handle 'select' actions from user interface
bool DefaultLogic::select(Window* wnd, const String& name, const String& item,
const String& text)
{
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) select name='%s' item='%s' in window (%p,%s)",
toString().c_str(),name.c_str(),item.c_str(),wnd,wnd ? wnd->id().c_str() : "");
if (name == s_accountList) {
if (!Client::valid())
return false;
ClientAccount* a = item ? m_accounts->findAccount(item) : 0;
NamedList p("");
fillAccLoginActive(p,a);
fillAccEditActive(p,!item.null() && !Client::self()->getVisible(s_wndAccount));
Client::self()->setParams(&p,wnd);
return true;
}
if (name == s_contactList) {
if (!Client::valid())
return false;
NamedList p("");
p.addParam("active:abk_call",String::boolText(!item.null()));
fillContactEditActive(p,true,&item,false);
Client::self()->setParams(&p,wnd);
return true;
}
if (name == s_chatContactList) {
enableChatActions(item ? m_accounts->findAnyContact(item) : 0);
return true;
}
if (name == s_mainwindowTabs) {
ClientContact* c = 0;
if (item == YSTRING("tabChat"))
c = selectedChatContact(*m_accounts,wnd);
else if (isPageCallsActive(wnd,false)) {
if (Client::valid())
Client::self()->ringer(true,false);
removeTrayIcon(YSTRING("incomingcall"));
}
enableChatActions(c,false);
return true;
}
// Item selected in calls log list
if (name == s_logList) {
if (!Client::self())
return false;
const char* active = String::boolText(!item.null());
NamedList p("");
p.addParam("active:log_call",active);
fillLogContactActive(p,true,&item);
Client::self()->setParams(&p,wnd);
return true;
}
// Specific select handlers
if (handleFileShareSelect(wnd,name,item,text,0))
return true;
// Page changed in telephony tab
if (name == YSTRING("framePages")) {
if (isPageCallsActive(wnd,true)) {
Client::self()->ringer(true,false);
removeTrayIcon(YSTRING("incomingcall"));
}
return false;
}
// Avoid sync with other contact add window
if (name == s_chatAccount)
return false;
// keep the item in sync in all windows
// if the same object is present in more windows, we will synchronise all of them
if (Client::self())
Client::self()->setSelect(name,item,0,wnd);
// Enable specific actions when a channel is selected
if (name == s_channelList) {
if (isPageCallsActive(wnd,true)) {
Client::self()->ringer(true,false);
removeTrayIcon(YSTRING("incomingcall"));
}
updateSelectedChannel(&item);
return true;
}
// when an account is selected, the choice of protocol must be cleared
// when a protocol is chosen, the choice of account must be cleared
bool acc = (name == YSTRING("account"));
if (acc || name == YSTRING("protocol")) {
Client::self()->setText(YSTRING("callto_hint"),YSTRING(""),false,wnd);
if (Client::s_notSelected.matches(item))
return true;
if (acc)
return Client::self()->setSelect(YSTRING("protocol"),s_notSelected,wnd);
return Client::self()->setSelect(YSTRING("account"),s_notSelected,wnd);
}
// Handle protocol/providers select in account edit/add or wizard
if (handleProtoProvSelect(wnd,name,item))
return true;
// Wizard select
if (s_accWizard->select(wnd,name,item,text) ||
s_mucWizard->select(wnd,name,item,text))
return true;
// Specific select handlers
if (handleMucsSelect(name,item,wnd,text))
return true;
// Selection changed in docked (room) chat
if (name == ClientContact::s_dockedChatWidget) {
if (item)
removePendingChat(item,m_accounts);
return true;
}
// No more notifications: remove the tray icon
if (name == YSTRING("messages")) {
if (!item) {
removeTrayIcon(YSTRING("notification"));
removeTrayIcon(YSTRING("info"));
}
return true;
}
// Selection changed in 'callto': do nothing. Just return true to avoid enqueueing ui.event
if (name == YSTRING("callto"))
return true;
return false;
}
// Handle 'select' with multiple items actions from user interface
bool DefaultLogic::select(Window* wnd, const String& name, const NamedList& items)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) select items=%p in window (%p,%s)",
toString().c_str(),&items,wnd,wnd ? wnd->id().c_str() : "");
// Specific select handlers
if (handleFileShareSelect(wnd,name,String::empty(),String::empty(),&items))
return true;
return false;
}
// Set a client's parameter. Save the settings file and/or update interface
bool DefaultLogic::setClientParam(const String& param, const String& value,
bool save, bool update)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) setClientParam(%s,%s,%s,%s)",
toString().c_str(),param.c_str(),value.c_str(),
String::boolText(save),String::boolText(update));
update = update && (0 != Client::self());
const char* section = 0;
bool changed = false;
// Bool params
Client::ClientToggle opt = Client::getBoolOpt(param);
if (opt != Client::OptCount) {
if (value.isBoolean()) {
section = "general";
if (Client::valid()) {
bool ok = value.toBoolean();
changed = Client::self()->setBoolOpt(opt,ok,update);
// Special care for some controls
if (opt == Client::OptKeypadVisible)
Client::self()->setShow(YSTRING("keypad"),ok);
if (changed && opt == Client::OptDockedChat) {
// Change contacts docked chat
s_changingDockedChat = true;
for (ObjList* o = m_accounts->accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (!a->hasChat())
continue;
for (ObjList* oo = a->contacts().skipNull(); oo; oo = oo->skipNext()) {
ClientContact* c = static_cast<ClientContact*>(oo->get());
changeDockedChat(*c,ok);
}
}
s_changingDockedChat = false;
}
// Clear notifications if disabled
if (opt == Client::OptNotifyChatState && !ok)
ContactChatNotify::clear();
}
}
}
else if (param == YSTRING("username") || param == YSTRING("callerid") ||
param == YSTRING("domain")) {
section = "default";
changed = true;
if (update)
Client::self()->setText("def_" + param,value);
}
if (!section)
return false;
if (!changed)
return true;
// Update/save settings
Client::s_settings.setValue(section,param,value);
if (save)
Client::save(Client::s_settings);
return true;
}
// Process an IM message
bool DefaultLogic::imIncoming(Message& msg)
{
bool stopLogic = false;
return defaultMsgHandler(msg,Client::MsgExecute,stopLogic);
}
// Call execute handler called by the client.
bool DefaultLogic::callIncoming(Message& msg, const String& dest)
{
if (!Client::self())
return false;
const String& fmt = msg[YSTRING("format")];
if (!fmt || fmt != YSTRING("data")) {
// Set params for incoming google voice call
if (msg[YSTRING("module")] == YSTRING("jingle")) {
URI uri(msg[YSTRING("callername")]);
if (uri.getHost() == YSTRING("voice.google.com")) {
msg.setParam("dtmfmethod","rfc2833");
msg.setParam("jingle_flags","noping");
}
}
return Client::self()->buildIncomingChannel(msg,dest);
}
if (!(msg.userData() && ClientDriver::self() && Client::self()))
return false;
CallEndpoint* peer = static_cast<CallEndpoint*>(msg.userData());
if (!peer)
return false;
String file = msg[YSTRING("file_name")];
Client::getLastNameInPath(file,file,'/');
Client::getLastNameInPath(file,file,'\\');
if (!file)
return false;
const String& oper = msg[YSTRING("operation")];
if (oper != YSTRING("receive"))
return false;
Message m(msg);
m.userData(msg.userData());
m.setParam("callto","dumb/");
if (!Engine::dispatch(m))
return false;
String targetid = m[YSTRING("targetid")];
if (!targetid)
return false;
msg.setParam("targetid",targetid);
static const String extra = "targetid,file_name,file_size,file_md5,file_time";
const String& contact = msg[YSTRING("callername")];
const String& account = msg[YSTRING("in_line")];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
ClientContact* c = a ? a->findContactByUri(contact) : 0;
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"incomingfile",account,contact,"Incoming file",extra);
upd->copyParams(msg,extra);
upd->setParam(YSTRING("file_name"),file);
String text;
text << "Incoming file '" << file << "'";
String buf;
if (c)
buildContactName(buf,*c);
else
buf = contact;
text.append(buf,"\r\nContact: ");
text.append(account,"\r\nAccount: ");
upd->addParam("text",text);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
return true;
}
static inline int targetExtraCharPos(const String& s)
{
for (unsigned int i = 0; i < s.length(); i++) {
char c = s[i];
if (c == '@' || c == ':')
return (int)i;
}
return -1;
}
// Validate an outgoing call
bool DefaultLogic::validateCall(NamedList& params, Window* wnd)
{
const String& ns = params[YSTRING("target")];
NamedString* proto = params.getParam(YSTRING("protocol"));
NamedString* acc = params.getParam(YSTRING("account"));
if (!acc)
acc = params.getParam(YSTRING("line"));
bool accountCleared = false;
int extraPos = -2;
if (!(proto && *proto == s_jabber) && !(acc && acc->startsWith("jabber:"))) {
int pos = ns.find('/');
if (pos > 0) {
params.clearParam(YSTRING("account"));
params.clearParam(YSTRING("line"));
params.clearParam(proto);
return true;
}
if (acc) {
extraPos = targetExtraCharPos(ns);
if (extraPos >= 0) {
accountCleared = true;
params.clearParam(YSTRING("account"));
params.clearParam(YSTRING("line"));
}
}
}
if (!TelEngine::null(acc))
return true;
const char* error = 0;
if (!TelEngine::null(proto)) {
if (extraPos >= 0 || (extraPos == -2 && targetExtraCharPos(ns) >= 0))
error = "This is not a valid protocol URI.";
}
else if (accountCleared)
error = "Invalid target for selected account.";
else
error = "You need a VoIP account to make calls.";
if (error)
Client::self()->setText(YSTRING("callto_hint"),error,false,wnd);
return error == 0;
}
// Start an outgoing call
bool DefaultLogic::callStart(NamedList& params, Window* wnd, const String& cmd)
{
if (!(Client::self() && fillCallStart(params,wnd)))
return false;
if (!validateCall(params,wnd))
return false;
String target;
const String& ns = params[YSTRING("target")];
if (cmd == s_actionCall) {
// Check google voice target on gmail accounts
String account = params.getValue(YSTRING("account"),params.getValue(YSTRING("line")));
if (account && isGmailAccount(m_accounts->findAccount(account))) {
// Allow calling user@domain
int pos = ns.find('@');
bool valid = (pos > 0) && (ns.find('.',pos + 2) >= pos);
if (!valid) {
target = ns;
Client::fixPhoneNumber(target,"().- ");
}
if (target) {
target = target + "@voice.google.com";
params.addParam("ojingle_version","0");
params.addParam("ojingle_flags","noping");
params.addParam("redirectcount","5");
params.addParam("checkcalled",String::boolText(false));
params.addParam("dtmfmethod","rfc2833");
String callParams = params[YSTRING("call_parameters")];
callParams.append("redirectcount,checkcalled,dtmfmethod,ojingle_version,ojingle_flags",",");
params.setParam("call_parameters",callParams);
}
else if (!valid) {
showError(wnd,"Incorrect number");
Debug(ClientDriver::self(),DebugNote,
"Failed to call: invalid gmail number '%s'",params.getValue("target"));
return false;
}
}
else if (account && isTigaseImAccount(m_accounts->findAccount(account))) {
// Allow calling user@domain
int pos = ns.find('@');
bool valid = (pos > 0) && (ns.find('.',pos + 2) >= pos);
if (!valid) {
target = ns;
Client::fixPhoneNumber(target,"().- ");
}
if (target) {
target = target + "@voip.tigase.im/yate";
// params.addParam("ojingle_version","0");
params.addParam("dtmfmethod","rfc2833");
params.addParam("offericeudp",String::boolText(false));
String callParams = params[YSTRING("call_parameters")];
callParams.append("dtmfmethod,ojingle_version,ojingle_flags,offericeudp",",");
params.setParam("call_parameters",callParams);
}
else if (!valid) {
showError(wnd,"Incorrect number");
Debug(ClientDriver::self(),DebugNote,
"Failed to call: invalid number '%s'",params.getValue("target"));
return false;
}
}
}
checkLoadModule(&params,target ? (const String*)&target : &ns);
// Delete the number from the "callto" widget and put it in the callto history
if (ns) {
Client::self()->delTableRow(s_calltoList,ns);
Client::self()->addOption(s_calltoList,ns,true);
Client::self()->setText(s_calltoList,"");
}
if (target)
params.setParam("target",target);
if (!Client::self()->buildOutgoingChannel(params))
return false;
// Activate the calls page
activatePageCalls();
return true;
}
// function which is called when a digit is pressed
bool DefaultLogic::digitPressed(NamedList& params, Window* wnd)
{
if (!Client::valid())
return false;
// Send digits (DTMF) on active channel
// or add them to 'callto' box
const String& digits = params[YSTRING("digits")];
if (!digits)
return false;
if (Client::self()->emitDigits(digits))
return true;
String target;
if (isE164(digits) && Client::self()->getText(YSTRING("callto"),target)) {
target += digits;
if (Client::self()->setText(YSTRING("callto"),target)) {
Client::self()->setFocus(YSTRING("callto"),false);
return true;
}
}
return false;
}
// Called when the user wants to add an account or edit an existing one
bool DefaultLogic::editAccount(bool newAcc, NamedList* params, Window* wnd)
{
return internalEditAccount(newAcc,0,params,wnd);
}
// Called when the user wants to save account data
bool DefaultLogic::acceptAccount(NamedList* params, Window* wnd)
{
if (!(Client::valid() && wnd))
return false;
NamedList p("");
if (!getAccount(wnd,p,*m_accounts))
return false;
const String& replace = wnd ? wnd->context() : String::empty();
if (replace) {
ClientAccount* edit = m_accounts->findAccount(replace);
if (edit) {
ClientAccount* acc = m_accounts->findAccount(p);
if (acc && acc != edit) {
// Don't know what to do: replace the duplicate or rename the editing one
showAccDupError(wnd);
return false;
}
}
}
if (!updateAccount(p,true,replace))
return false;
// Hide the window. Save some settings
Client::self()->setVisible(wnd->toString(),false);
Client::s_settings.setValue(YSTRING("client"),"acc_protocol",p["protocol"]);
Client::save(Client::s_settings);
return true;
}
// Called when the user wants to delete an existing account
bool DefaultLogic::delAccount(const String& account, Window* wnd)
{
if (!account)
return deleteSelectedItem(s_accountList + ":",wnd);
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc)
return false;
// Disconnect
Engine::enqueue(userLogin(acc,false));
// Delete from memory and UI. Save the accounts file
m_ftManager->cancel(acc->toString());
removeAccNotifications(acc);
closeAccPasswordWnd(account);
closeAccCredentialsWnd(account);
removeAccountShareInfo(acc);
clearAccountContacts(*acc);
updateChatRoomsContactList(false,acc);
Client::self()->delTableRow(s_account,account);
Client::self()->delTableRow(s_accountList,account);
acc->save(false);
String error;
if (!acc->clearDataDir(&error) && error)
notifyGenericError(error,account);
m_accounts->removeAccount(account);
return true;
}
// Add/set an account
bool DefaultLogic::updateAccount(const NamedList& account, bool login, bool save)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) updateAccount(%s,%s,%s)",
toString().c_str(),account.c_str(),String::boolText(login),String::boolText(save));
// Load account status if not already done
AccountStatus::load();
if (!Client::valid() || account.null())
return false;
return updateAccount(account,false,String::empty(),true);
}
// Login/logout an account
bool DefaultLogic::loginAccount(const NamedList& account, bool login)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) loginAccount(%s,%s)",
toString().c_str(),account.c_str(),String::boolText(login));
Message* m = 0;
ClientAccount* acc = m_accounts->findAccount(account);
ClientResource::Status newStat = ClientResource::Unknown;
if (acc) {
m = userLogin(acc,login);
if (login) {
checkLoadModule(&acc->params());
if (acc->resource().offline() || !isTelProto(acc->protocol()))
newStat = ClientResource::Connecting;
}
else {
newStat = ClientResource::Offline;
// Don't show a notification when disconnected
if (!login)
acc->m_params.setParam("internal.nologinfail",String::boolText(true));
}
}
else {
m = Client::buildMessage("user.login",account,login ? "login" : "logout");
if (login) {
m->copyParams(account);
checkLoadModule(&account);
}
else
m->copyParams(account,YSTRING("protocol"));
}
Engine::enqueue(m);
if (newStat != ClientResource::Unknown) {
acc->resource().setStatus(newStat);
acc->resource().setStatusText("");
updateAccountStatus(acc,m_accounts);
}
return true;
}
// Add/update a contact
bool DefaultLogic::updateContact(const NamedList& params, bool save, bool update)
{
if (!(Client::valid() && (save || update) && params))
return false;
const String& target = params[YSTRING("target")];
if (!target)
return false;
// Fix contact id
String id;
String pref;
ClientContact::buildContactId(pref,m_accounts->localContacts()->toString(),String::empty());
if (params.startsWith(pref,false))
id = params;
else
ClientContact::buildContactId(id,m_accounts->localContacts()->toString(),params);
ClientContact* c = m_accounts->findContact(id);
if (!c)
c = new ClientContact(m_accounts->localContacts(),params,id,target);
else if (c) {
const String& name = params[YSTRING("name")];
if (name)
c->m_name = name;
c->setUri(target);
}
else
return false;
// Update UI
if (update)
updateContactList(*c);
// Save file
bool ok = true;
if (save && m_accounts->isLocalContact(c)) {
String name;
c->getContactSection(name);
unsigned int n = params.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = params.getParam(i);
if (!ns)
continue;
if (*ns)
Client::s_contacts.setValue(name,ns->name(),*ns);
else
Client::s_contacts.clearKey(name,ns->name());
}
ok = Client::save(Client::s_contacts);
}
// Notify server if this is a client account (stored on server)
// TODO: implement
return ok;
}
// Called when the user wants to save contact data
bool DefaultLogic::acceptContact(NamedList* params, Window* wnd)
{
if (!Client::valid())
return false;
const char* err = 0;
String id;
String name;
String target;
// Check required data
while (true) {
#define SET_ERR_BREAK(e) { err = e; break; }
Client::self()->getText(YSTRING("abk_name"),name,false,wnd);
if (!name)
SET_ERR_BREAK("A contact name must be specified");
Client::self()->getText(YSTRING("abk_target"),target,false,wnd);
if (!target)
SET_ERR_BREAK("Contact number/target field can't be empty");
// Check if adding/editing contact. Generate a new contact id
if (wnd && wnd->context())
id = wnd->context();
else {
String tmp;
tmp << (unsigned int)Time::msecNow() << "_" << (int)Engine::runId();
ClientContact::buildContactId(id,m_accounts->localContacts()->toString(),tmp);
}
ClientContact* existing = m_accounts->localContacts()->findContact(id);
ClientContact* dup = 0;
if (existing) {
if (existing->m_name == name && existing->uri() == target) {
// No changes: return
if (wnd)
Client::self()->setVisible(wnd->toString(),false);
return true;
}
dup = m_accounts->localContacts()->findContact(&name,0,&id);
}
else
dup = m_accounts->localContacts()->findContact(&name);
if (dup)
SET_ERR_BREAK("A contact with the same name already exists!");
break;
#undef SET_ERR_BREAK
}
if (err) {
Client::openMessage(err,wnd);
return false;
}
NamedList p(id);
p.addParam("name",name);
p.addParam("target",target);
if (!updateContact(p,true,true))
return false;
if (wnd)
Client::self()->setVisible(wnd->toString(),false);
return true;
}
// Called when the user wants to add a new contact or edit an existing one
bool DefaultLogic::editContact(bool newCont, NamedList* params, Window* wnd)
{
if (!Client::valid())
return false;
// Make sure we reset all controls in window
NamedList p("");
if (newCont) {
p.addParam("abk_name",params ? params->c_str() : "");
p.addParam("abk_target",params ? params->getValue(YSTRING("target")) : "");
}
else {
String cont;
Client::self()->getSelect(s_contactList,cont);
ClientContact* c = cont ? m_accounts->findContactByInstance(cont) : 0;
if (!(c && m_accounts->isLocalContact(c)))
return false;
p.addParam("context",c->toString());
p.addParam("abk_name",c->m_name);
p.addParam("abk_target",c->uri());
}
return Client::openPopup(s_wndAddrbook,&p);
}
// Called when the user wants to delete an existing contact
bool DefaultLogic::delContact(const String& contact, Window* wnd)
{
if (!Client::valid())
return false;
if (!contact)
return deleteSelectedItem(s_contactList + ":",wnd);
ClientContact* c = m_accounts->findContactByInstance(contact);
if (!(c && m_accounts->isLocalContact(c)))
return false;
// Update shared
c->clearShare();
updateContactShareInfo(c,false);
m_ftManager->cancel(c->accountName(),contact);
// Delete the contact from config and all UI controls
contactDeleted(*c);
String sectName;
c->getContactSection(sectName);
Client::s_contacts.clearSection(sectName);
String id = c->toString();
m_accounts->localContacts()->removeContact(id);
Client::save(Client::s_contacts);
return true;
}
// Add/set account providers data
bool DefaultLogic::updateProviders(const NamedList& provider, bool save, bool update)
{
if (!(save || update))
return false;
if (provider.null() || !provider.getBoolValue(YSTRING("enabled"),true))
return false;
if (save && !Client::save(Client::s_providers))
return false;
return updateProvidersItem(0,s_accProviders,provider);
}
// Called when the user wants to call an existing contact
bool DefaultLogic::callContact(NamedList* params, Window* wnd)
{
if (!Client::valid())
return false;
NamedList dummy("");
if (!params) {
String sel;
Client::self()->getSelect(s_contactList,sel);
dummy.assign(sel);
params = &dummy;
}
if (!Client::self()->getTableRow(s_contactList,*params,params))
return false;
const String& target = (*params)[YSTRING("number/uri")];
if (!target)
return false;
bool call = true;
String account;
String proto;
String cmd;
ClientContact* c = m_accounts->findContactByInstance(*params);
if (!m_accounts->isLocalContact(c)) {
// Not a local contact: check if it belongs to registered account
if (c && c->account() && c->account()->resource().online()) {
account = c->account()->toString();
proto = c->account()->protocol();
}
call = !account.null();
}
else {
static const Regexp r("^[a-z0-9]\\+/");
if (!r.matches(target)) {
Client::self()->getSelect(s_account,account);
call = !account.null();
if (call)
cmd = s_actionCall;
}
}
if (call) {
NamedList p("");
p.addParam("line",account,false);
p.addParam("account",account,false);
p.addParam("target",target);
p.addParam("protocol",proto,false);
return callStart(p,0,cmd);
}
Client::self()->setText(s_calltoList,target);
activatePageCalls();
return true;
}
// Update the call log history
bool DefaultLogic::callLogUpdate(const NamedList& params, bool save, bool update)
{
if (!(save || update))
return false;
String* bid = params.getParam(YSTRING("billid"));
const String& id = bid ? (const String&)(*bid) : params[YSTRING("id")];
if (!id)
return false;
if (Client::valid() && update) {
// Remember: directions are opposite of what the user expects
const String& dir = params[YSTRING("direction")];
bool outgoing = (dir == YSTRING("incoming"));
if (outgoing || dir == YSTRING("outgoing")) {
// Skip if there is no remote party
const String& party = cdrRemoteParty(params,outgoing);
NamedList p("");
String time;
unsigned int t = (unsigned int)params.getDoubleValue(YSTRING("time"));
Client::self()->formatDateTime(time,t,"yyyy.MM.dd hh:mm",false);
p.addParam("party",party);
p.addParam("party_image",Client::s_skinPath +
(outgoing ? "outgoing.png" : "incoming.png"));
p.addParam("time",time);
time.clear();
unsigned int d = (unsigned int)params.getDoubleValue(YSTRING("duration"));
Client::self()->formatDateTime(time,d,"hh:mm:ss",true);
p.addParam("duration",time);
Client::self()->updateTableRow(s_logList,id,&p);
}
else
Debug(ClientDriver::self(),DebugNote,
"Failed to add CDR to history, unknown direction='%s'",
dir.c_str());
}
if (!save)
return true;
// Update the call history file
// We don't hold information for more than s_maxCallHistory, so if we reached the
// limit, we delete the oldest entry to make room
while (Client::s_history.sections() >= s_maxCallHistory) {
NamedList* sect = Client::s_history.getSection(0);
if (!sect)
break;
Client::s_history.clearSection(*sect);
}
// Write to the file the information about the calls
NamedList* sect = Client::s_history.createSection(id);
if (!sect)
return false;
*sect = params;
sect->assign(id);
return Client::save(Client::s_history);
}
// Remove a call log item
bool DefaultLogic::callLogDelete(const String& billid)
{
if (!billid)
return false;
bool ok = true;
if (Client::valid())
ok = Client::self()->delTableRow(s_logList,billid);
NamedList* sect = Client::s_history.getSection(billid);
if (!sect)
return ok;
Client::s_history.clearSection(*sect);
return Client::save(Client::s_history) && ok;
}
// Clear the specified log and the entries from the history file and save the history file
bool DefaultLogic::callLogClear(const String& table, const String& direction)
{
// Clear history
bool save = false;
unsigned int n = Client::s_history.sections();
if (direction)
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = Client::s_history.getSection(i);
NamedString* dir = sect ? sect->getParam(YSTRING("direction")) : 0;
if (!dir || *dir != direction)
continue;
Client::s_history.clearSection(*sect);
save = true;
i--;
}
else {
save = (0 != n);
Client::s_history.clearSection();
}
// Clear table and save the file
if (Client::self())
Client::self()->clearTable(table);
if (save)
Client::save(Client::s_history);
return true;
}
// Make an outgoing call to a target picked from the call log
bool DefaultLogic::callLogCall(const String& billid, Window* wnd)
{
NamedList* sect = Client::s_history.getSection(billid);
if (!sect)
return false;
const String& party = cdrRemoteParty(*sect);
return party && action(wnd,"callto:" + party);
}
// Create a contact from a call log entry
bool DefaultLogic::callLogCreateContact(const String& billid)
{
NamedList* sect = Client::s_history.getSection(billid);
if (!sect)
return false;
const String& party = cdrRemoteParty(*sect);
NamedList p(party);
p.setParam("target",party);
return editContact(true,&p);
}
// Process help related actions
bool DefaultLogic::help(const String& name, Window* wnd)
{
if (!Client::self())
return false;
Window* help = Client::self()->getWindow("help");
if (!help)
return false;
// Set the the searched page
bool show = false;
int page = help->context().toInteger();
if (name == YSTRING("help:home"))
page = 0;
else if (name == YSTRING("help:prev"))
page--;
else if (name == YSTRING("help:next"))
page++;
else if (name.startsWith("help:")) {
page = name.substr(5).toInteger(page);
show = true;
}
if (page < 0)
page = 0;
// Get the help file from the help folder
String helpFile = Engine::config().getValue(YSTRING("client"),"helpbase");
if (!helpFile)
helpFile << Engine::sharedPath() << Engine::pathSeparator() << "help";
if (!helpFile.endsWith(Engine::pathSeparator()))
helpFile << Engine::pathSeparator();
helpFile << page << ".yhlp";
File f;
if (!f.openPath(helpFile)) {
Debug(ClientDriver::self(),DebugNote,"Failed to open help file '%s'",helpFile.c_str());
return false;
}
// if the opening of the help file succeeds, we set it as the text of the help window
int rd = 0;
unsigned int len = (unsigned int)f.length();
if (len != (unsigned int)-1) {
String helpText(' ',len);
rd = f.readData(const_cast<char*>(helpText.c_str()),len);
if (rd == (int)len) {
Client::self()->setText(YSTRING("help_text"),helpText,true,help);
help->context(String(page));
if (show)
Client::self()->setVisible(YSTRING("help"),true);
return true;
}
}
Debug(ClientDriver::self(),DebugNote,"Read only %d out of %u bytes in file '%s'",
rd,len,helpFile.c_str());
return false;
}
// Called by the client after loaded the callto history file
bool DefaultLogic::calltoLoaded()
{
if (!Client::self())
return false;
NamedList* sect = Client::s_calltoHistory.getSection(YSTRING("calls"));
if (!sect)
return false;
unsigned int n = sect->length();
unsigned int max = 0;
for (unsigned int i = 0; max < s_maxCallHistory && i < n; i++) {
NamedString* s = sect->getParam(i);
if (!s || Client::self()->hasOption(s_calltoList,s->name()))
continue;
if (Client::self()->addOption(s_calltoList,s->name(),false))
max++;
}
Client::self()->setText(s_calltoList,"");
return false;
}
// Process ui.action message
bool DefaultLogic::handleUiAction(Message& msg, bool& stopLogic)
{
if (!Client::self())
return false;
// get action
NamedString* action = msg.getParam(YSTRING("action"));
if (!action)
return false;
// block until client finishes initialization
while (!Client::self()->initialized())
Thread::idle();
// call the appropiate function for the given action
Window* wnd = 0;
const String& wndName = msg[YSTRING("window")];
if (wndName) {
// Window was requested by message, do nothing if not found
wnd = Client::getWindow(wndName);
if (!wnd)
return false;
}
if (*action == YSTRING("set_status"))
return Client::self()->setStatusLocked(msg.getValue(YSTRING("status")),wnd);
else if (*action == YSTRING("add_log"))
return Client::self()->addToLog(msg.getValue(YSTRING("text")));
else if (*action == YSTRING("show_message")) {
Client::self()->lockOther();
bool ok = Client::openMessage(msg.getValue(YSTRING("text")),
Client::getWindow(msg.getValue(YSTRING("parent"))),msg.getValue(YSTRING("context")));
Client::self()->unlockOther();
return ok;
}
else if (*action == YSTRING("show_confirm")) {
Client::self()->lockOther();
bool ok = Client::openConfirm(msg.getValue(YSTRING("text")),
Client::getWindow(msg.getValue(YSTRING("parent"))),msg.getValue(YSTRING("context")));
Client::self()->unlockOther();
return ok;
}
else if (*action == YSTRING("notify_error")) {
const String* text = msg.getParam(YSTRING("text"));
if (TelEngine::null(text))
return false;
Client::self()->lockOther();
notifyGenericError(*text,msg.getValue(YSTRING("account")),
msg.getValue(YSTRING("contact")),msg.getValue(YSTRING("title")));
Client::self()->unlockOther();
return true;
}
// get the name of the widget for which the action is meant
String name(msg.getValue(YSTRING("name")));
if (name.null())
return false;
DDebug(ClientDriver::self(),DebugAll,"UI action '%s' on '%s' in %p",
action->c_str(),name.c_str(),wnd);
bool ok = false;
Client::self()->lockOther();
if (*action == YSTRING("set_text"))
ok = Client::self()->setText(name,msg.getValue(YSTRING("text")),false,wnd);
else if (*action == YSTRING("set_toggle"))
ok = Client::self()->setCheck(name,msg.getBoolValue(YSTRING("active")),wnd);
else if (*action == YSTRING("set_select"))
ok = Client::self()->setSelect(name,msg.getValue(YSTRING("item")),wnd);
else if (*action == YSTRING("set_active"))
ok = Client::self()->setActive(name,msg.getBoolValue(YSTRING("active")),wnd);
else if (*action == YSTRING("set_focus"))
ok = Client::self()->setFocus(name,msg.getBoolValue(YSTRING("select")),wnd);
else if (*action == YSTRING("set_visible"))
ok = Client::self()->setShow(name,msg.getBoolValue(YSTRING("visible")),wnd);
else if (*action == YSTRING("set_property"))
ok = Client::self()->setProperty(name,msg[YSTRING("property")],msg[YSTRING("value")],wnd);
else if (*action == YSTRING("has_option"))
ok = Client::self()->hasOption(name,msg.getValue(YSTRING("item")),wnd);
else if (*action == YSTRING("add_option"))
ok = Client::self()->addOption(name,msg.getValue(YSTRING("item")),
msg.getBoolValue(YSTRING("insert")),msg.getValue(YSTRING("text")),wnd);
else if (*action == YSTRING("del_option"))
ok = Client::self()->delTableRow(name,msg.getValue(YSTRING("item")),wnd);
else if (*action == YSTRING("get_text")) {
String text;
ok = Client::self()->getText(name,text,false,wnd);
if (ok)
msg.retValue() = text;
}
else if (*action == YSTRING("get_toggle")) {
bool check;
ok = Client::self()->getCheck(name,check,wnd);
if (ok)
msg.retValue() = check;
}
else if (*action == YSTRING("get_select")) {
String item;
ok = Client::self()->getSelect(name,item,wnd);
if (ok)
msg.retValue() = item;
}
else if (*action == YSTRING("window_show"))
ok = Client::setVisible(name,true);
else if (*action == YSTRING("window_hide"))
ok = Client::setVisible(name,false);
else if (*action == YSTRING("window_popup"))
ok = Client::openPopup(name,&msg,Client::getWindow(msg[YSTRING("parent")]));
Client::self()->unlockOther();
return ok;
}
// Process call.cdr message
bool DefaultLogic::handleCallCdr(Message& msg, bool& stopLogic)
{
if (!Client::self())
return false;
if (msg[YSTRING("operation")] != YSTRING("finalize"))
return false;
if (!msg[YSTRING("chan")].startsWith("client/",false))
return false;
if (Client::self()->postpone(msg,Client::CallCdr,false))
stopLogic = true;
else
callLogUpdate(msg,true,true);
return false;
}
// Process user.login message
bool DefaultLogic::handleUserLogin(Message& msg, bool& stopLogic)
{
return false;
}
// Process user.notify message
bool DefaultLogic::handleUserNotify(Message& msg, bool& stopLogic)
{
if (!Client::valid())
return false;
if (Client::self()->postpone(msg,Client::UserNotify,false)) {
stopLogic = true;
return false;
}
const String& account = msg[YSTRING("account")];
if (!account)
return false;
bool reg = msg.getBoolValue(YSTRING("registered"));
m_ftManager->handleResourceNotify(reg,account);
const String& reasonStr = msg[YSTRING("reason")];
const char* reason = reasonStr;
// Notify wizards
s_mucWizard->handleUserNotify(account,reg,reason);
bool save = s_accWizard->handleUserNotify(account,reg,reason);
bool fromWiz = save;
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc)
return false;
// Always remove roster request notification when account status changed
removeNotifArea(YSTRING("rosterreqfail"),account);
// Notify status
String txt = reg ? "Registered" : "Unregistered";
txt << " account " << account;
txt.append(reason," reason: ");
Client::self()->setStatusLocked(txt);
int stat = ClientResource::Online;
String regStat;
if (reg) {
// Remove account failure notification if still there
removeNotifArea(YSTRING("loginfail"),account);
closeAccPasswordWnd(account);
closeAccCredentialsWnd(account);
// Clear account register option
NamedString* opt = acc->m_params.getParam(YSTRING("options"));
if (opt) {
ObjList* list = opt->split(',',false);
ObjList* o = list->find(YSTRING("register"));
if (o) {
save = true;
o->remove();
opt->clear();
opt->append(list,",");
if (opt->null())
acc->m_params.clearParam(opt);
}
TelEngine::destruct(list);
}
acc->resource().m_id = msg.getValue(YSTRING("instance"));
// Set account status from pending data
int tmp = acc->params().getIntValue(YSTRING("internal.status.status"),ClientResource::s_statusName);
if (tmp > stat)
stat = tmp;
regStat = acc->params().getValue(YSTRING("internal.status.text"));
// Update chat accounts. Request MUCs
if (acc->hasChat()) {
updateChatAccountList(account,true);
Engine::enqueue(acc->userData(false,"chatrooms"));
// Auto join rooms
for (ObjList* o = acc->mucs().skipNull(); o; o = o->skipNext()) {
MucRoom* r = static_cast<MucRoom*>(o->get());
if (r->m_params.getBoolValue(YSTRING("autojoin")) &&
checkGoogleRoom(r->uri()))
joinRoom(r);
}
}
}
else {
bool noFail = acc->params().getBoolValue(YSTRING("internal.nologinfail"));
bool reConn = acc->params().getBoolValue(YSTRING("internal.reconnect"));
// Show login failure message if not requested by the user
if (!(noFail || reConn)) {
const String& error = msg[YSTRING("error")];
bool noAuth = isNoAuth(reasonStr,error);
String text(noAuth ? "Login failed for account '" : "Failed to connect account '");
text << account << "'";
if (reasonStr || error) {
text << "\r\nReason: ";
if (reasonStr) {
text << reasonStr;
if (error && reasonStr != error)
text << " (" << error << ")";
}
else
text << error;
}
if (!(noAuth && getAccCredentialsWnd(acc->params(),true,text))) {
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"loginfail",account,String::empty(),"Login failure");
upd->addParam("text",text);
// Enable/disable account edit
const char* ok = String::boolText(!Client::self()->getVisible(s_wndAccount));
upd->addParam("active:messages_acc_edit",ok);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
}
else {
// Remove account failure notification if still there
removeNotifArea(YSTRING("loginfail"),account);
}
}
if (msg.getBoolValue(YSTRING("autorestart")))
stat = ClientResource::Connecting;
else {
if (!reConn) {
stat = ClientResource::Offline;
if (s_chatLog == ChatLogSaveUntilLogout)
logClearAccount(account);
}
else {
stat = ClientResource::Connecting;
acc->m_params.clearParam(YSTRING("internal.reconnect"));
// Re-connect the account
Message* m = userLogin(acc,true);
addAccPendingStatus(*m,acc);
Engine::enqueue(m);
// Clear the reason to avoid displaying it (we requested the disconnect)
reason = 0;
}
// Reset resource name to configured
acc->resource().m_id = acc->m_params.getValue(YSTRING("resource"));
}
removeAccountShareInfo(acc);
clearAccountContacts(*acc);
setOfflineMucs(acc);
// Remove from chat accounts
if (acc->hasChat())
updateChatAccountList(account,false);
}
// (Un)Load chat rooms
updateChatRoomsContactList(reg,acc);
// Clear some internal params
acc->m_params.clearParam(YSTRING("internal.nologinfail"));
if (stat != ClientResource::Connecting)
acc->m_params.clearParam(YSTRING("internal.status"),'.');
bool changed = acc->resource().setStatus(stat);
changed = acc->resource().setStatusText(reg ? regStat.c_str() : reason) || changed;
if (changed)
updateAccountStatus(acc,m_accounts);
else if (!reg)
PendingRequest::clear(acc->toString());
if (save)
acc->save(true,acc->params().getBoolValue(YSTRING("savepassword")));
// Update telephony account selector(s)
updateTelAccList(acc->startup() && reg,acc);
setAdvancedMode();
// Added from wizard
// Update account status to server: notify presence and request roster or
// disconnect it of global presence is 'offline'
if (fromWiz) {
if (AccountStatus::current() &&
AccountStatus::current()->status() != ClientResource::Offline) {
if (!isTelProto(acc->protocol())) {
Message* m = Client::buildNotify(true,acc->toString(),
acc->resource(false));
Engine::enqueue(m);
queryRoster(acc);
}
}
else
setAccountStatus(m_accounts,acc);
}
return false;
}
// Process user.roster message
bool DefaultLogic::handleUserRoster(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& oper = msg[YSTRING("operation")];
if (!oper)
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::UserRoster)) {
stopLogic = true;
return false;
}
const String& account = msg[YSTRING("account")];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
if (!a)
return false;
if (oper == YSTRING("error") || oper == YSTRING("queryerror") ||
oper == YSTRING("result")) {
showUserRosterNotification(a,oper,msg,msg[YSTRING("contact")]);
return false;
}
bool remove = (oper != YSTRING("update"));
if (remove && oper != YSTRING("delete"))
return false;
int n = msg.getIntValue(YSTRING("contact.count"));
if (n < 1)
return false;
bool queryRsp = msg.getBoolValue(YSTRING("queryrsp"));
if (queryRsp)
removeNotifArea(YSTRING("rosterreqfail"),account);
ObjList removed;
NamedList chatlist("");
for (int i = 1; i <= n; i++) {
String pref("contact." + String(i));
const String& uri = msg[pref];
if (!uri)
continue;
String id;
ClientContact::buildContactId(id,account,uri);
ClientContact* c = a->findContact(id);
// Avoid account's own contact
if (c && c == a->contact())
continue;
if (remove) {
if (!c)
continue;
if (!queryRsp)
showUserRosterNotification(a,oper,msg,uri);
c->clearShare();
updateContactShareInfo(c,false);
m_ftManager->cancel(c->accountName(),c->uri());
removed.append(a->removeContact(id,false));
continue;
}
pref << ".";
// Add/update contact
const char* cName = msg.getValue(pref + "name",uri);
bool newContact = (c == 0);
bool changed = newContact;
if (c)
changed = setChangedString(c->m_name,cName) || changed;
else {
c = a->appendContact(id,cName,uri);
if (!c)
continue;
}
const String& sub = msg[pref + "subscription"];
bool hadSub = c->subscriptionFrom();
if (c->setSubscription(sub)) {
changed = true;
if (c->subscriptionFrom()) {
if (!hadSub) {
updateContactShareInfo(c,true);
notifyContactShareInfoChanged(c);
// Request shared
for (ObjList* o = c->resources().skipNull(); o; o = o->skipNext()) {
ClientResource* res = static_cast<ClientResource*>(o->get());
if (res->caps().flag(ClientResource::CapFileInfo))
SharedPendingRequest::start(c,res);
}
}
}
else {
updateContactShareInfo(c,false);
m_ftManager->cancel(c->accountName(),c->uri());
}
}
// Get groups
changed = c->setGroups(msg,pref + "group") || changed;
if (changed) {
// Update info window if displayed
updateContactInfo(c);
// Show update notification
if (!queryRsp)
showUserRosterNotification(a,oper,msg,uri,newContact);
}
if (!(changed && a->hasChat()))
continue;
NamedList* p = new NamedList(c->toString());
fillChatContact(*p,*c,true,newContact);
showChatContactActions(*c,p);
chatlist.addParam(new NamedPointer(c->toString(),p,String::boolText(true)));
if (c->hasChat())
c->updateChatWindow(*p,"Chat [" + c->m_name + "]");
}
// Update UI and share
for (ObjList* o = removed.skipNull(); o; o = o->skipNext())
contactDeleted(*static_cast<ClientContact*>(o->get()));
Client::self()->updateTableRows(s_chatContactList,&chatlist,false);
return true;
}
// Process resource.notify message
bool DefaultLogic::handleResourceNotify(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& contact = msg[YSTRING("contact")];
if (!contact)
return false;
const String& oper = msg[YSTRING("operation")];
if (!oper)
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::ResourceNotify)) {
stopLogic = true;
return false;
}
const String& account = msg[YSTRING("account")];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
if (!a)
return false;
const String& inst = msg[YSTRING("instance")];
if (msg.getBoolValue(YSTRING("muc")))
return handleMucResNotify(msg,a,contact,inst,oper);
ClientContact* c = a->findContactByUri(contact);
if (!c)
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s contact=%s instance=%s operation=%s",
name().c_str(),account.c_str(),contact.c_str(),inst.safe(),oper.c_str());
bool ownContact = c == a->contact();
String instid;
bool online = false;
bool statusChanged = false;
bool oldOnline = c->online();
ClientResource* res = 0;
bool hadFileSharedCap = c->haveShared();
bool hadFileSharedCapRes = false;
bool hadFileTransfer = (0 != c->findFileTransferResource());
// Use a while() to break to the end
while (true) {
// Avoid account own instance
if (ownContact && inst && inst == a->resource().toString())
return false;
online = (oper == YSTRING("online"));
bool updateCaps = !online && oper == YSTRING("updatecaps");
if (online || updateCaps || oper == YSTRING("offline")) {
if (online || updateCaps) {
if (online && c->subscriptionFrom())
m_ftManager->handleResourceNotify(true,account,contact,inst);
if (updateCaps) {
res = c->findResource(inst);
if (res) {
hadFileSharedCapRes = (0 != res->caps().flag(ClientResource::CapFileInfo));
int caps = Client::decodeFlags(ClientResource::s_resNotifyCaps,msg,YSTRING("caps."));
res->caps().change(caps);
if (res->caps().flag(ClientResource::CapAudio))
instid = inst;
}
break;
}
c->setOnline(true);
if (!inst) {
statusChanged = !oldOnline;
break;
}
statusChanged = true;
res = c->findResource(inst);
if (res)
hadFileSharedCapRes = (0 != res->caps().flag(ClientResource::CapFileInfo));
else
res = new ClientResource(inst);
// Update resource
int caps = Client::decodeFlags(ClientResource::s_resNotifyCaps,msg,YSTRING("caps."));
res->caps().change(caps);
res->setPriority(msg.getIntValue(YSTRING("priority")));
res->setStatusText(msg.getValue(YSTRING("status")));
int stat = msg.getIntValue(YSTRING("show"),ClientResource::s_statusName);
if (stat < ClientResource::Online)
stat = ClientResource::Online;
res->setStatus(stat);
// (Re)insert the resource
c->insertResource(res);
// Update/set resource in contacts list (only for resources with audio caps)
if (res->caps().flag(ClientResource::CapAudio))
instid = inst;
}
else {
PendingRequest::cancel(c,inst);
bool sharedChanged = false;
ClientDir* removed = 0;
if (inst) {
statusChanged = c->removeResource(inst);
if (!c->resources().skipNull()) {
statusChanged = statusChanged || oldOnline;
c->setOnline(false);
}
sharedChanged = c->removeShared(inst,&removed);
}
else {
if (c->online()) {
statusChanged = true;
c->resources().clear();
c->setOnline(false);
}
sharedChanged = c->removeShared();
}
m_ftManager->handleResourceNotify(false,account,contact,inst);
// Remove resource(s) from contacts list
c->buildInstanceId(instid,inst);
if (sharedChanged) {
// Remove shared items from UI
removeSharedFromUI(c,removed);
TelEngine::destruct(removed);
}
}
break;
}
// TODO: handle other operations like received errors
break;
}
// Online resource
if (res) {
if (c->subscriptionFrom()) {
bool haveSharedFileCap = (0 != res->caps().flag(ClientResource::CapFileInfo));
if (!hadFileSharedCapRes && haveSharedFileCap)
SharedPendingRequest::start(c,res,String::empty(),true,0,1000000);
}
}
if (hadFileSharedCap != c->haveShared() ||
hadFileTransfer != (0 != c->findFileTransferResource())) {
enableChatActions(c,true,true,true);
showChatContactActions(*c);
}
if (instid) {
if (online)
updateContactList(*c,instid,msg.getValue("uri"));
else
removeContacts(instid);
}
if (statusChanged) {
NamedList p("");
fillChatContact(p,*c,false,true);
Client::self()->setTableRow(s_chatContactList,c->toString(),&p);
if (c->hasChat()) {
bool newOnline = c->online();
ClientResource* res = c->status();
int stat = newOnline ? ClientResource::Online : ClientResource::Offline;
c->updateChatWindow(p,0,resStatusImage(res ? res->m_status : stat));
if (oldOnline != newOnline)
addChatNotify(*c,newOnline,false,msg.msgTime().sec());
}
// Update info window if displayed
updateContactInfo(c);
// Update chat actions in main contacts list (main window)
String sel;
Client::self()->getSelect(s_chatContactList,sel,Client::self()->getWindow(s_wndMain));
if (c->toString() == sel)
enableChatActions(c);
}
return false;
}
// Process resource.subscribe message
bool DefaultLogic::handleResourceSubscribe(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& account = msg[YSTRING("account")];
const String& contact = msg[YSTRING("subscriber")];
const String& oper = msg[YSTRING("operation")];
if (!(account && contact && oper))
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::ResourceSubscribe)) {
stopLogic = true;
return false;
}
ClientAccount* a = m_accounts->findAccount(account);
if (!a)
return false;
bool sub = (oper == YSTRING("subscribe"));
if (!sub && oper != YSTRING("unsubscribe"))
return false;
ClientContact* c = a->findContactByUri(contact);
if (c && c == a->contact())
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s contact=%s operation=%s",
name().c_str(),account.c_str(),contact.c_str(),oper.c_str());
if (sub && a->resource().online()) {
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"subscription",account,contact,"Subscription request");
String cname;
if (c && c->m_name && (c->m_name != contact))
cname << "'" << c->m_name << "' ";
upd->addParam("name",cname);
String s = "Contact ${name}<${contact}> requested subscription on account '${account}'.";
upd->replaceParams(s);
upd->addParam("text",s);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
}
return true;
}
// Process chan.startup message
bool DefaultLogic::handleClientChanUpdate(Message& msg, bool& stopLogic)
{
#define CHANUPD_ID (chan ? chan->id() : *id)
#define CHANUPD_ADDR (chan ? chan->address() : String::empty())
if (!Client::self())
return false;
if (Client::self()->postpone(msg,Client::ClientChanUpdate,true)) {
stopLogic = true;
return false;
}
// Ignore utility channels (playing sounds)
if (msg.getBoolValue(YSTRING("utility")))
return false;
int notif = ClientChannel::lookup(msg.getValue(YSTRING("notify")));
if (notif == ClientChannel::Destroyed) {
if (!Client::valid())
return false;
const String& id = msg[YSTRING("id")];
closeInCallNotification(id);
int slave = ClientChannel::lookupSlaveType(msg.getValue("channel_slave_type"));
if (slave) {
bool conf = (slave == ClientChannel::SlaveConference);
const String& masterId = msg[YSTRING("channel_master")];
if (masterId) {
ClientChannel* master = ClientDriver::findChan(masterId);
unsigned int slaves = 0;
if (master) {
master->removeSlave(id);
slaves = master->slavesCount();
TelEngine::destruct(master);
}
NamedList p("");
int items = channelItemAdjustUiList(p,-1,false,masterId,conf);
if (conf) {
if (slaves) {
String tmp;
tmp << "Conference (" << (slaves + 1) << ")";
p.addParam("status",tmp);
}
else
channelItemBuildUpdate(false,p,masterId,true,false,masterId);
}
channelItemBuildUpdate(false,p,masterId,conf,false,id);
// Add transfer start
if (!conf && !slaves && items)
channelItemBuildUpdate(true,p,masterId,false,true);
Client::self()->setTableRow(s_channelList,masterId,&p);
if (!slaves) {
if (conf)
ClientDriver::setConference(masterId,false);
}
}
}
s_generic.clearParam(id,'_');
// Reset init transfer if destroyed
if (m_transferInitiated && m_transferInitiated == id)
m_transferInitiated = "";
// Stop incoming ringer if there are no more incoming channels
bool haveIncoming = false;
if (ClientDriver::self()) {
Lock lock(ClientDriver::self());
ObjList* o = ClientDriver::self()->channels().skipNull();
for (; o; o = o->skipNext())
if ((static_cast<Channel*>(o->get()))->isOutgoing()) {
haveIncoming = true;
break;
}
}
if (!haveIncoming) {
removeTrayIcon(YSTRING("incomingcall"));
Client::self()->ringer(true,false);
Client::self()->ringer(false,false);
}
Client::self()->delTableRow(s_channelList,id);
enableCallActions(m_selectedChannel);
String status;
buildStatus(status,"Hung up",msg.getValue("address"),id,msg.getValue("reason"));
Client::self()->setStatusLocked(status);
return false;
}
// Set some data from channel
ClientChannel* chan = static_cast<ClientChannel*>(msg.userData());
// We MUST have an ID
NamedString* id = 0;
if (!chan)
id = msg.getParam(YSTRING("id"));
if (!(chan || id))
return false;
bool outgoing = chan ? chan->isOutgoing() : msg.getBoolValue(YSTRING("outgoing"));
bool noticed = chan ? chan->isNoticed() : msg.getBoolValue(YSTRING("noticed"));
bool active = chan ? chan->active() : msg.getBoolValue(YSTRING("active"));
bool silence = msg.getBoolValue(YSTRING("silence"));
bool notConf = !(chan ? chan->conference() : msg.getBoolValue(YSTRING("conference")));
// Stop ringing on not silenced active outgoing channels
if (active && !outgoing && !silence)
Client::self()->ringer(false,false);
// Add slaves to master channels
int slave = chan ? chan->slave() : ClientChannel::SlaveNone;
if (slave) {
const String& masterId = chan->master();
ClientChannel* master = ClientDriver::findChan(masterId);
if (!master) {
ClientDriver::dropChan(chan->id());
return false;
}
if (notif == ClientChannel::Startup) {
// Update master
bool conf = (slave == ClientChannel::SlaveConference);
if (conf || slave == ClientChannel::SlaveTransfer) {
NamedList p("");
master->addSlave(chan->id());
channelItemAdjustUiList(p,-1,true,masterId,conf);
if (conf) {
int n = master->slavesCount();
if (n == 1) {
if (master->hasReconnPeer())
channelItemBuildUpdate(true,p,masterId,conf,false,masterId);
}
String tmp;
tmp << "Conference (" << (n + 1) << ")";
p.addParam("status",tmp);
}
else
channelItemBuildUpdate(false,p,masterId,conf,true);
channelItemBuildUpdate(true,p,masterId,conf,false,chan->id());
Client::self()->setTableRow(s_channelList,masterId,&p);
}
}
TelEngine::destruct(master);
}
// Update UI
NamedList p("");
bool updateFormats = !slave;
bool enableActions = false;
bool setStatus = !slave && notConf && !chan->transferId();
String status;
switch (notif) {
case ClientChannel::Active:
buildStatus(status,"Call active",CHANUPD_ADDR,CHANUPD_ID);
if (slave)
break;
enableActions = true;
updateFormats = false;
Client::self()->setSelect(s_channelList,CHANUPD_ID);
setImageParam(p,"status_image","activ.png",false);
if (outgoing) {
if (noticed)
Client::self()->ringer(true,false);
closeInCallNotification(CHANUPD_ID);
}
else {
Client::self()->ringer(true,false);
if (silence)
Client::self()->ringer(false,true);
}
break;
case ClientChannel::AudioSet:
if (chan) {
bool mic = chan->muted() || (0 != chan->getSource());
bool speaker = (0 != chan->getConsumer());
notifyNoAudio(!(mic && speaker),mic,speaker,chan);
}
break;
case ClientChannel::OnHold:
buildStatus(status,"Call inactive",CHANUPD_ADDR,CHANUPD_ID);
if (slave)
break;
enableActions = true;
setImageParam(p,"status_image","hold.png",false);
if (outgoing) {
if (noticed)
Client::self()->ringer(true,false);
closeInCallNotification(CHANUPD_ID);
}
else {
Client::self()->ringer(true,false);
Client::self()->ringer(false,false);
}
break;
case ClientChannel::Ringing:
buildStatus(status,"Call ringing",CHANUPD_ADDR,CHANUPD_ID);
break;
case ClientChannel::Noticed:
// Stop incoming ringer
Client::self()->ringer(true,false);
buildStatus(status,"Call noticed",CHANUPD_ADDR,CHANUPD_ID);
closeInCallNotification(CHANUPD_ID);
break;
case ClientChannel::Progressing:
buildStatus(status,"Call progressing",CHANUPD_ADDR,CHANUPD_ID);
break;
case ClientChannel::Startup:
if (slave)
break;
enableActions = true;
// Create UI entry
if (chan && Client::self()->addTableRow(s_channelList,CHANUPD_ID,&p)) {
DurationUpdate* d = new DurationUpdate(this,false,CHANUPD_ID,"time");
chan->setClientData(d);
TelEngine::destruct(d);
}
else
return false;
if (outgoing) {
addTrayIcon(YSTRING("incomingcall"));
Client::self()->setUrgent(s_wndMain,true,Client::self()->getWindow(s_wndMain));
showInCallNotification(chan);
}
p.addParam("active:answer",String::boolText(outgoing));
p.addParam("party",chan ? chan->party() : "");
p.addParam("status",outgoing ? "Incoming" : "Outgoing");
setImageParam(p,"direction",outgoing ? "incoming.png" : "outgoing.png",false);
setImageParam(p,"status_image",active ? "active.png" : "hold.png",false);
p.addParam("show:frame_items",String::boolText(false));
// Start incoming ringer if there is no active channel
if (outgoing && notConf) {
ClientChannel* ch = ClientDriver::findActiveChan();
if (!ch)
Client::self()->ringer(true,true);
else
TelEngine::destruct(ch);
}
setStatus = false;
break;
case ClientChannel::Accepted:
buildStatus(status,"Calling target",0,0);
break;
case ClientChannel::Answered:
if (outgoing) {
removeTrayIcon(YSTRING("incomingcall"));
closeInCallNotification(CHANUPD_ID);
}
buildStatus(status,"Call answered",CHANUPD_ADDR,CHANUPD_ID);
// Stop incoming ringer
Client::self()->ringer(true,false);
if (active)
Client::self()->ringer(false,false);
if (slave)
break;
enableActions = true;
p.addParam("active:answer",String::boolText(false));
break;
case ClientChannel::Routed:
updateFormats = false;
buildStatus(status,"Calling",chan ? chan->party() : "",0,0);
break;
case ClientChannel::Rejected:
updateFormats = false;
buildStatus(status,"Call failed",CHANUPD_ADDR,CHANUPD_ID,msg.getValue("reason"));
break;
case ClientChannel::Transfer:
updateFormats = false;
if (slave)
break;
if (chan->transferId())
p.addParam("status","Transferred");
break;
case ClientChannel::Conference:
updateFormats = false;
if (slave)
break;
break;
default:
enableActions = true;
updateFormats = false;
buildStatus(status,String("Call notification=") + msg.getValue("notify"),
CHANUPD_ADDR,CHANUPD_ID);
}
if (enableActions && m_selectedChannel == CHANUPD_ID)
enableCallActions(m_selectedChannel);
if (status)
Client::self()->setStatusLocked(status);
if (updateFormats && chan) {
String fmt;
fmt << (chan->peerOutFormat() ? chan->peerOutFormat().c_str() : "-");
fmt << "/";
fmt << (chan->peerInFormat() ? chan->peerInFormat().c_str() : "-");
p.addParam("format",fmt);
}
if (setStatus && chan) {
String s;
chan->getStatus(s);
s = s.substr(0,1).toUpper() + s.substr(1);
p.setParam("status",s);
}
if (!slave)
Client::self()->setTableRow(s_channelList,CHANUPD_ID,&p);
return false;
#undef CHANUPD_ID
#undef CHANUPD_ADDR
}
// Process contact.info message
bool DefaultLogic::handleContactInfo(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& account = msg[YSTRING("account")];
if (!account)
return false;
const String& oper = msg[YSTRING("operation")];
if (!oper)
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::ContactInfo)) {
stopLogic = true;
return false;
}
const String& contact = msg[YSTRING("contact")];
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s)::handleContactInfo() account=%s contact=%s operation=%s",
name().c_str(),account.c_str(),contact.c_str(),oper.c_str());
// Notify the MUC wizard
s_mucWizard->handleContactInfo(msg,account,oper,contact);
return false;
}
// Default message processor called for id's not defined in client.
bool DefaultLogic::defaultMsgHandler(Message& msg, int id, bool& stopLogic)
{
if (id == Client::ChanNotify) {
String event = msg.getValue(YSTRING("event"));
if (event != YSTRING("left"))
return false;
// Check if we have a channel in conference whose peer is the one who left
const String& peer = msg[YSTRING("lastpeerid")];
ClientChannel* chan = ClientDriver::findChanByPeer(peer);
if (!chan)
return false;
if (chan->conference()) {
DDebug(ClientDriver::self(),DebugInfo,
"Channel %s left the conference. Terminating %s",
peer.c_str(),chan->id().c_str());
// Don't drop master chan with slaves
if (chan->slave() != ClientChannel::SlaveNone || !chan->slavesCount()) {
// Try to use Client's way first
if (Client::self())
Client::self()->callTerminate(chan->id());
else
chan->disconnect("Peer left the conference");
}
else if (chan->slave() == ClientChannel::SlaveNone) {
// Remove master peer from list in client's thread
if (!Client::self()->postpone(msg,id,true)) {
NamedList p("");
channelItemAdjustUiList(p,-1,false,chan->id(),true);
channelItemBuildUpdate(false,p,chan->id(),true,false,chan->id());
Client::self()->setTableRow(s_channelList,chan->id(),&p);
}
else
stopLogic = true;
}
}
TelEngine::destruct(chan);
return false;
}
if (id == Client::MsgExecute) {
if (!Client::valid() || Client::isClientMsg(msg))
return false;
if (Client::self()->postpone(msg,Client::MsgExecute))
return true;
const String& account = msg[YSTRING("account")];
if (!account)
return false;
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc)
return false;
const String& type = msg[YSTRING("type")];
String tmp;
ClientContact::buildContactId(tmp,account,msg.getValue(YSTRING("caller")));
ClientContact* c = acc->findContact(tmp);
bool chat = (!type || type == YSTRING("chat"));
if (c) {
if (chat) {
String* delay = msg.getParam(YSTRING("delay_time"));
unsigned int time = !delay ? msg.msgTime().sec() : (unsigned int)delay->toInteger(0);
const char* ds = !delay ? "" : msg.getValue(YSTRING("delay_by"));
String chatState;
bool hasState = !delay && buildChatState(chatState,msg,c->m_name);
const String& body = msg[YSTRING("body")];
NamedList* p = 0;
if (body || (!hasState &&
Client::self()->getBoolOpt(Client::OptShowEmptyChat)))
p = buildChatParams(body,c->m_name,time,0 != delay,ds);
// Active state with no body or notification: remove last notification
// if the contact has a chat
bool resetNotif = false;
if (c->hasChat())
resetNotif = !p && !chatState && msg[YSTRING("chatstate")] == YSTRING("active");
else
chatState.clear();
if (p || chatState || resetNotif) {
if (!c->hasChat()) {
c->createChatWindow();
NamedList p("");
fillChatContact(p,*c,true,true);
fillChatContactShareStatus(p,*c,false,true);
ClientResource* res = c->status();
c->updateChatWindow(p,"Chat [" + c->m_name + "]",
resStatusImage(res ? res->m_status : ClientResource::Offline));
}
c->showChat(true);
if (chatState)
addChatNotify(*c,chatState,msg.msgTime().sec(),"tempnotify");
if (p) {
logChat(c,time,false,delay != 0,body);
c->addChatHistory(!delay ? YSTRING("chat_in") : YSTRING("chat_delayed"),p);
notifyIncomingChat(c);
}
if (resetNotif)
c->setChatProperty(YSTRING("history"),YSTRING("_yate_tempitemcount"),String((int)0));
}
}
else
DDebug(ClientDriver::self(),DebugStub,
"DefaultLogic unhandled message type=%s",type.c_str());
return true;
}
MucRoom* room = acc->findRoom(tmp);
if (!room)
return false;
bool mucChat = !chat && type == YSTRING("groupchat");
if (!(mucChat || chat)) {
Debug(ClientDriver::self(),DebugStub,
"DefaultLogic unhandled MUC message type=%s",type.c_str());
return true;
}
const String& body = msg[YSTRING("body")];
String* delay = mucChat ? msg.getParam(YSTRING("delay_time")) : 0;
const String& nick = msg[YSTRING("caller_instance")];
MucRoomMember* member = room->findMember(nick);
// Accept delayed (history) group chat from unknown nick
if (!member && !(mucChat && delay))
return false;
unsigned int time = !delay ? msg.msgTime().sec() : (unsigned int)delay->toInteger(0);
// Check subject changes (empty subject is allowed)
String* subject = mucChat ? msg.getParam(YSTRING("subject")) : 0;
if (subject) {
NamedList tmp("");
tmp.addParam("room_subject",*subject);
room->updateChatWindow(room->resource().toString(),tmp);
// Show any notification from room
if (body)
addChatNotify(*room,body,msg.msgTime().sec());
String text(nick);
text << " changed room subject to '" << *subject << "'";
if (delay) {
NamedList* p = buildChatParams(text,"",time,0,0);
room->addChatHistory(room->resource().toString(),YSTRING("chat_delayed"),p);
notifyIncomingChat(room,room->resource().toString());
}
else
addChatNotify(*room,text,msg.msgTime().sec());
return true;
}
// Ignore non delayed chat returned by the room
if (!delay && (!member || room->ownMember(member)))
return true;
String chatState;
bool hasState = !delay && buildChatState(chatState,msg,member->m_name);
NamedList* p = 0;
if (body || (!hasState &&
Client::self()->getBoolOpt(Client::OptShowEmptyChat)))
p = buildChatParams(body,member ? member->m_name : nick,time,0,0);
const String& id = mucChat ? room->resource().toString() : member->toString();
// Active state with no body or notification: remove last notification
bool resetNotif = false;
if (room->hasChat(id))
resetNotif = !p && !chatState && msg[YSTRING("chatstate")] == YSTRING("active");
else
chatState.clear();
if (p || chatState || resetNotif) {
if (chat)
createRoomChat(*room,member,false);
if (chatState)
addChatNotify(*room,chatState,msg.msgTime().sec(),"tempnotify",id);
if (p) {
room->addChatHistory(id,!delay ? YSTRING("chat_in") : YSTRING("chat_delayed"),p);
notifyIncomingChat(room,id);
if (body)
logChat(room,time,false,delay != 0,body,mucChat,nick);
}
if (resetNotif)
room->setChatProperty(id,YSTRING("history"),YSTRING("_yate_tempitemcount"),String((int)0));
}
return true;
}
if (id == Client::MucRoom) {
static const char* extra = "room,password,reason,contact_instance";
if (!Client::valid() || Client::isClientMsg(msg))
return false;
if (Client::self()->postpone(msg,Client::MucRoom))
return true;
const String& account = msg[YSTRING("account")];
ClientAccount* acc = account ? m_accounts->findAccount(account) : 0;
if (!acc)
return false;
const String& oper = msg[YSTRING("operation")];
const String& room = msg[YSTRING("room")];
String tmp;
if (room)
ClientContact::buildContactId(tmp,account,room);
MucRoom* r = tmp ? acc->findRoom(tmp) : 0;
// Invite
if (oper == YSTRING("invite")) {
// Account already there
if (r && r->resource().online())
return false;
const String& contact = msg[YSTRING("contact")];
if (!contact) {
Message* m = buildMucRoom("decline",account,room,"Unnaceptable anonymous invitation!");
return Engine::enqueue(m);
}
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"mucinvite",account,contact,"Join chat room",extra);
upd->copyParams(msg,extra);
String cname;
ClientContact* c = acc->findContactByUri(contact);
if (c && c->m_name && (c->m_name != contact))
cname << "'" << c->m_name << "' ";
upd->addParam("name",cname);
String s = "Contact ${name}<${contact}> invites you to join chat room '${room}' on account '${account}'.\r\n${reason}";
upd->replaceParams(s);
upd->addParam("text",s);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
return true;
}
return false;
}
if (id == Client::TransferNotify)
return handleFileTransferNotify(msg,stopLogic);
if (id == Client::UserData)
return handleUserData(msg,stopLogic);
if (id == Client::FileInfo)
return handleFileInfo(msg,stopLogic);
return false;
}
// Client created and initialized all windows
void DefaultLogic::initializedWindows()
{
if (!Client::valid())
return;
// Add 'not selected' item
Client::self()->updateTableRow(YSTRING("protocol"),s_notSelected,0,true);
Client::self()->updateTableRow(s_accProviders,s_notSelected,0,true);
Client::self()->updateTableRow(YSTRING("account"),s_notSelected,0,true);
// Fill protocol lists
bool tel = true;
updateProtocolList(0,YSTRING("protocol"),&tel);
updateProtocolList(0,s_accProtocol);
// Make sure the active page is the calls one
activatePageCalls(0,false);
}
// Utility: set check parameter from another list
static inline void setCheck(NamedList& p, const NamedList& src, const String& param,
bool defVal = true)
{
bool ok = src.getBoolValue(param,defVal);
p.addParam("check:" + param,String::boolText(ok));
}
// Initialize client from settings
bool DefaultLogic::initializedClient()
{
if (!Client::self())
return false;
addTrayIcon("main");
// Load account status
AccountStatus::load();
AccountStatus::updateUi();
// Load muc rooms
s_mucRooms = Engine::configFile("client_mucrooms",true);
s_mucRooms.load(false);
Window* wMain = Client::self()->getWindow(s_wndMain);
NamedList dummy("client");
NamedList* cSect = Client::s_settings.getSection("client");
if (!cSect)
cSect = &dummy;
NamedList* cGen = Client::s_settings.getSection("general");
if (!cGen)
cGen = &dummy;
// Check if global settings override the users'
bool globalOverride = Engine::config().getBoolValue("client","globaloverride",false);
// Booleans
for (unsigned int i = 0; i < Client::OptCount; i++) {
bool tmp = Client::self()->getBoolOpt((Client::ClientToggle)i);
bool active = true;
if (globalOverride) {
String* over = Engine::config().getKey("client",Client::s_toggles[i]);
if (over) {
tmp = over->toBoolean(tmp);
active = false;
}
else
tmp = cGen->getBoolValue(Client::s_toggles[i],tmp);
}
else {
tmp = Engine::config().getBoolValue("client",Client::s_toggles[i],tmp);
tmp = cGen->getBoolValue(Client::s_toggles[i],tmp);
}
Client::self()->setActive(Client::s_toggles[i],active);
setClientParam(Client::s_toggles[i],String::boolText(tmp),false,true);
}
setAdvancedMode();
// Other string parameters
setClientParam("username",Client::s_settings.getValue("default","username"),false,true);
setClientParam("callerid",Client::s_settings.getValue("default","callerid"),false,true);
setClientParam("domain",Client::s_settings.getValue("default","domain"),false,true);
// Create default ring sound
// Try to build native for wave file
String ring = cGen->getValue("ringinfile",Client::s_soundPath + "ring.wav");
bool wave = ring.endsWith(".wav");
if (!(wave && Client::self()->createSound(Client::s_ringInName,ring))) {
if (wave)
ring = Client::s_soundPath + "ring.au";
ClientSound::build(Client::s_ringInName,ring);
}
ring = cGen->getValue("ringoutfile",Client::s_soundPath + "tone.wav");
Client::self()->createSound(Client::s_ringOutName,ring);
// Enable call actions
enableCallActions(m_selectedChannel);
// Set handlers
Client::self()->installRelay("chan.notify",Client::ChanNotify,100);
Client::self()->installRelay("muc.room",Client::MucRoom,100);
Client::self()->installRelay("transfer.notify",Client::TransferNotify,100);
Client::self()->installRelay("user.data",Client::UserData,100);
Client::self()->installRelay("file.info",Client::FileInfo,100);
// File transfer
s_lastFileDir = Client::s_settings.getValue("filetransfer","dir");
s_lastFileFilter = Client::s_settings.getValue("filetransfer","filter");
s_lastFileShareDir = Client::s_settings.getValue("filetransfer","share_dir");
// Chat log
int v = lookup(cSect->getValue("logchat"),s_chatLogDict);
if (v == ChatLogSaveAll || v == ChatLogSaveUntilLogout || v == ChatLogNoSave)
s_chatLog = (ChatLogEnum)v;
// Update settings
NamedList p("");
// Chat contacts list options
String tmp;
Client::self()->getProperty(s_chatContactList,"_yate_showofflinecontacts",tmp,wMain);
p.addParam("check:" + s_chatShowOffline,String(tmp.toBoolean(true)));
tmp.clear();
Client::self()->getProperty(s_chatContactList,"_yate_flatlist",tmp,wMain);
p.addParam("check:" + s_chatFlatList,String(tmp.toBoolean(true)));
tmp.clear();
Client::self()->getProperty(s_chatContactList,"_yate_hideemptygroups",tmp,wMain);
p.addParam("check:chatcontact_hideemptygroups",String(tmp.toBoolean(true)));
// Show last page in main tab
p.addParam("select:" + s_mainwindowTabs,cSect->getValue("main_active_page","tabChat"));
// Settings
p.addParam("check:" + String(lookup(s_chatLog,s_chatLogDict)),String::boolText(true));
// Account edit defaults
setCheck(p,*cSect,"acc_showadvanced",false);
setCheck(p,*cSect,"acc_enabled");
Client::self()->setParams(&p);
// Build chat contacts context menu(s)
NamedList pcm(s_chatContactList);
NamedList* pChat = new NamedList("menu_" + s_chatContactList);
pChat->addParam("item:" + s_chatNew,"");
pChat->addParam("item:" + s_chatRoomNew,"");
pChat->addParam("item:","");
pChat->addParam("item:" + s_chatShowOffline,"");
pChat->addParam("item:" + s_chatFlatList,"");
pcm.addParam(new NamedPointer("menu",pChat));
NamedList* pChatMenu = new NamedList("menu_" + s_chatContactList + "_contact");
pChatMenu->addParam("item:" + s_chat,"");
pChatMenu->addParam("item:" + s_chatCall,"");
pChatMenu->addParam("item:" + s_fileSend,"");
pChatMenu->addParam("item:" + s_fileShare,"");
pChatMenu->addParam("item:" + s_fileShared,"");
pChatMenu->addParam("item:" + s_chatShowLog,"");
pChatMenu->addParam("item:" + s_chatInfo,"");
pChatMenu->addParam("item:" + s_chatEdit,"");
pChatMenu->addParam("item:" + s_chatDel,"");
pChatMenu->addParam("item:","");
pChatMenu->addParam("item:" + s_chatNew,"");
pChatMenu->addParam("item:" + s_chatRoomNew,"");
pChatMenu->addParam("item:","");
pChatMenu->addParam("item:" + s_chatShowOffline,"");
pChatMenu->addParam("item:" + s_chatFlatList,"");
pcm.addParam(new NamedPointer("contactmenu",pChatMenu));
NamedList* pChatRoomMenu = new NamedList("menu_" + s_chatContactList + "_chatroom");
pChatRoomMenu->addParam("item:" + s_chat,"");
pChatRoomMenu->addParam("item:" + s_chatShowLog,"");
pChatRoomMenu->addParam("item:" + s_chatEdit,"");
pChatRoomMenu->addParam("item:" + s_chatDel,"");
pChatRoomMenu->addParam("item:","");
pChatRoomMenu->addParam("item:" + s_chatNew,"");
pChatRoomMenu->addParam("item:" + s_chatRoomNew,"");
pChatRoomMenu->addParam("item:","");
pChatRoomMenu->addParam("item:" + s_chatShowOffline,"");
pChatRoomMenu->addParam("item:" + s_chatFlatList,"");
pcm.addParam(new NamedPointer("chatroommenu",pChatRoomMenu));
Client::self()->setParams(&pcm);
enableChatActions(0);
// Set gobal account status menu
NamedList pStatusMenu("");
pStatusMenu.addParam("owner","global_account_status");
pStatusMenu.addParam("item:setStatusOnline","");
pStatusMenu.addParam("item:setStatusBusy","");
pStatusMenu.addParam("item:setStatusAway","");
pStatusMenu.addParam("item:setStatusXa","");
pStatusMenu.addParam("item:setStatusDnd","");
pStatusMenu.addParam("item:","");
pStatusMenu.addParam("item:setStatusOffline","");
Client::self()->buildMenu(pStatusMenu);
// Activate the main window if not disabled from UI
if (wMain) {
String a;
Client::self()->getProperty(wMain->id(),"_yate_activateonstartup",a,wMain);
if (a.toBoolean(true))
Client::self()->setActive(wMain->id(),true,wMain);
}
return false;
}
// Client is exiting: save settings
void DefaultLogic::exitingClient()
{
clearDurationUpdate();
if (!Client::valid())
return;
// Avoid open account add the next time we start if the user closed the window
if (!Client::self()->getVisible(s_accWizard->toString()))
setClientParam(Client::s_toggles[Client::OptAddAccountOnStartup],
String(false),true,false);
// Reset wizards
s_accWizard->reset(true);
s_mucWizard->reset(true);
Client::self()->setVisible(s_accWizard->toString(),false);
Client::self()->setVisible(s_mucWizard->toString(),false);
// Hide some windows to avoid displaying them the next time we start
Client::self()->setVisible(s_wndAccount,false);
Client::self()->setVisible(s_wndChatContact,false);
Client::self()->setVisible(ClientContact::s_dockedChatWnd,false);
Client::self()->setVisible(s_wndAddrbook,false);
Client::self()->setVisible(s_wndMucInvite,false);
Client::self()->setVisible(s_wndFileTransfer,false);
// Save some settings identity
String tmp;
if (Client::self()->getText("def_username",tmp))
Client::s_settings.setValue("default","username",tmp);
tmp.clear();
if (Client::self()->getText("def_callerid",tmp))
Client::s_settings.setValue("default","callerid",tmp);
tmp.clear();
if (Client::self()->getText("def_domain",tmp))
Client::s_settings.setValue("default","domain",tmp);
tmp.clear();
Window* wMain = Client::self()->getWindow(s_wndMain);
if (wMain)
Client::self()->getSelect(s_mainwindowTabs,tmp,wMain);
Client::s_settings.setValue("client","main_active_page",tmp);
Client::save(Client::s_settings);
// Save callto history
NamedList p("");
if (Client::self()->getOptions(s_calltoList,&p)) {
NamedList* sect = Client::s_calltoHistory.createSection("calls");
sect->clearParams();
unsigned int n = p.length();
unsigned int max = 0;
for (unsigned int i = 0; max < s_maxCallHistory && i < n; i++) {
NamedString* s = p.getParam(i);
if (!s)
continue;
max++;
sect->addParam(s->name(),*s);
}
Client::save(Client::s_calltoHistory);
}
}
// Update from UI the selected item in channels list
void DefaultLogic::updateSelectedChannel(const String* item)
{
String old = m_selectedChannel;
if (item)
m_selectedChannel = *item;
else if (Client::self())
Client::self()->getSelect(s_channelList,m_selectedChannel);
else
m_selectedChannel = "";
if (old == m_selectedChannel)
return;
// Stop incoming ringer
if (Client::valid())
Client::self()->ringer(true,false);
channelSelectionChanged(old);
}
// Engine start notification. Connect startup accounts
void DefaultLogic::engineStart(Message& msg)
{
// Set account status or start add wizard
if (m_accounts->accounts().skipNull())
setAccountsStatus(m_accounts);
else if (Client::valid() &&
Client::self()->getBoolOpt(Client::OptAddAccountOnStartup)) {
// Start add account wizard
s_accWizard->start();
}
}
// Show incoming call notification for a given channel
void DefaultLogic::showInCallNotification(ClientChannel* chan)
{
if (!(chan && Client::valid()))
return;
Window* w = Client::self()->getWindow(s_wndNotification);
if (!w)
return;
Client::self()->setVisible(s_wndNotification,false);
NamedList p("");
p.addParam("context",chan->id());
p.addParam("property:answeraction:_yate_identity","answer:" + chan->id());
p.addParam("property:hangupaction:_yate_identity","hangup:" + chan->id());
String text = "Incoming call";
if (chan->party())
text << " from " << chan->party();
p.addParam("text",text);
Client::self()->setParams(&p,w);
Client::self()->setVisible(s_wndNotification);
}
// Close incoming call notification for a given id
void DefaultLogic::closeInCallNotification(const String& id)
{
if (!(id && Client::valid()))
return;
Window* w = Client::self()->getWindow(s_wndNotification);
if (w && w->context() == id) {
NamedList p("");
p.addParam("context","");
Client::self()->setParams(&p,w);
Client::self()->closeWindow(s_wndNotification);
}
}
// Method called by the client when idle
void DefaultLogic::idleTimerTick(Time& time)
{
for (ObjList* o = m_durationUpdate.skipNull(); o; o = o->skipNext())
(static_cast<DurationUpdate*>(o->get()))->update(time.sec(),&s_channelList);
if (Client::valid() && Client::self()->getBoolOpt(Client::OptNotifyChatState) &&
ContactChatNotify::checkTimeouts(*m_accounts,time))
Client::setLogicsTick();
// Pending requests
PendingRequest::s_mutex.lock();
for (ObjList* o = PendingRequest::s_items.skipNull(); o; o = o->skipNext())
(static_cast<PendingRequest*>(o->get()))->sendPendingMsg(time);
PendingRequest::s_mutex.unlock();
}
// Enable call actions
bool DefaultLogic::enableCallActions(const String& id)
{
if (!Client::self())
return false;
ClientChannel* chan = id.null() ? 0 : ClientDriver::findChan(id);
DDebug(ClientDriver::self(),DebugInfo,"enableCallActions(%s) chan=%p",
id.c_str(),chan);
NamedList p("");
// Answer/Hangup/Hold
p.addParam("active:" + s_actionAnswer,String::boolText(chan && chan->isOutgoing() && !chan->isAnswered()));
p.addParam("active:" + s_actionHangup,String::boolText(0 != chan));
p.addParam("active:" + s_actionHold,String::boolText(chan != 0));
p.addParam("check:" + s_actionHold,String::boolText(chan && chan->active()));
// Transfer
// Not allowed on conference channels
bool active = false;
bool checked = false;
bool conf = chan && chan->conference();
if (chan && !conf) {
Lock lock(chan->driver());
if (chan->driver() && chan->driver()->channels().count() > 1)
active = true;
lock.drop();
checked = (0 != chan->transferId());
}
p.addParam("active:" + s_actionTransfer,String::boolText(active));
p.addParam("check:" + s_actionTransfer,String::boolText(active && checked));
// Activate/deactivate conference button
active = (0 != chan && chan->isAnswered());
p.addParam("active:" + s_actionConf,String::boolText(active));
p.addParam("check:" + s_actionConf,String::boolText(active && conf));
TelEngine::destruct(chan);
Client::self()->setParams(&p);
return true;
}
// Fill call start parameter list from UI
bool DefaultLogic::fillCallStart(NamedList& p, Window* wnd)
{
if (!checkParam(p,YSTRING("target"),YSTRING("callto"),false,wnd))
return false;
checkParam(p,YSTRING("line"),YSTRING("account"),true,wnd);
checkParam(p,YSTRING("protocol"),YSTRING("protocol"),true,wnd);
checkParam(p,YSTRING("account"),YSTRING("account"),true,wnd);
checkParam(p,YSTRING("caller"),YSTRING("def_username"),false);
checkParam(p,YSTRING("callername"),YSTRING("def_callerid"),false);
checkParam(p,YSTRING("domain"),YSTRING("def_domain"),false);
return true;
}
// Notification on selection changes in channels list
void DefaultLogic::channelSelectionChanged(const String& old)
{
DDebug(ClientDriver::self(),DebugInfo,"channelSelectionChanged() to '%s' old='%s'",
m_selectedChannel.c_str(),old.c_str());
while (true) {
// Check if the transfer button was pressed
if (m_transferInitiated && m_transferInitiated == old) {
m_transferInitiated = "";
bool transfer = false;
if (Client::self())
Client::self()->getCheck(s_actionTransfer,transfer);
if (transfer) {
if (ClientDriver::setAudioTransfer(old,m_selectedChannel))
break;
else if (Client::self())
Client::self()->setStatusLocked("Failed to transfer");
}
}
m_transferInitiated = "";
// Set the active channel
if (Client::self()->getBoolOpt(Client::OptActivateCallOnSelect) &&
m_selectedChannel && ClientDriver::self())
ClientDriver::self()->setActive(m_selectedChannel);
break;
}
enableCallActions(m_selectedChannel);
}
// Fill contact edit/delete active parameters
void DefaultLogic::fillContactEditActive(NamedList& list, bool active, const String* item,
bool del)
{
if (active) {
if (!Client::self())
return;
if (!Client::self()->getVisible(s_wndAddrbook))
active = isLocalContact(item,m_accounts,s_contactList);
else
active = false;
}
const char* ok = String::boolText(active);
if (del)
list.addParam("active:abk_del",ok);
list.addParam("active:abk_edit",ok);
}
// Fill log contact active parameter
void DefaultLogic::fillLogContactActive(NamedList& list, bool active, const String* item)
{
if (active) {
if (!Client::self())
return;
if (!Client::self()->getVisible(s_wndAddrbook)) {
if (item)
active = !item->null();
else {
String sel;
active = Client::self()->getSelect(s_logList,sel) && sel;
}
}
else
active = false;
}
list.addParam("active:log_contact",String::boolText(active));
}
// Clear a list/table. Handle specific lists like CDR, accounts, contacts
bool DefaultLogic::clearList(const String& action, Window* wnd)
{
if (!(Client::valid() && action))
return false;
// Check for a confirmation text
int pos = action.find(":");
String list;
if (pos > 0)
list = action.substr(0,pos);
else if (pos < 0)
list = action;
if (!list)
return false;
if (pos > 0) {
String text = action.substr(pos + 1);
if (!text) {
// Handle some known lists
if (list == s_logList)
text = "Clear call history?";
}
if (text)
return showConfirm(wnd,text,"clear:" + list);
}
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::clearList(%s,%p)",
list.c_str(),wnd);
// Handle CDR
if (list == s_logList)
return callLogClear(s_logList,String::empty());
bool ok = Client::self()->clearTable(list,wnd) || Client::self()->setText(list,"",false,wnd);
if (ok)
Client::self()->setFocus(list,false,wnd);
return ok;
}
// Delete a list/table item. Handle specific lists like CDR
bool DefaultLogic::deleteItem(const String& list, const String& item, Window* wnd, bool confirm)
{
if (!(Client::valid() && list && item))
return false;
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::deleteItem(%s,%s,%p,%u)",
list.c_str(),item.c_str(),wnd,confirm);
String context;
if (confirm)
context << "deleteitem:" << list << ":" << item;
// Handle known lists
if (list == s_chatContactList) {
ClientContact* c = m_accounts->findAnyContact(item);
if (!c)
return false;
MucRoom* r = c->mucRoom();
if (context) {
String text("Delete ");
text << (!r ? "friend " : "chat room ");
String name;
buildContactName(name,*c);
text << name << " from account '" << c->accountName() << "'?";
return showConfirm(wnd,text,context);
}
if (!r)
Engine::enqueue(Client::buildUserRoster(false,c->accountName(),c->uri()));
else {
ClientAccount* acc = r->account();
bool saveServerRooms = (acc != 0) && r->remote();
if (acc)
ClientLogic::clearContact(acc->m_cfg,r);
updateChatRoomsContactList(false,0,r);
r->setLocal(false);
r->setRemote(false);
if (saveServerRooms)
Engine::enqueue(acc->userData(true,"chatrooms"));
}
return true;
}
if (list == s_contactList) {
if (context) {
ClientContact* c = m_accounts->findContactByInstance(item);
if (!(c && m_accounts->isLocalContact(c)))
return false;
return showConfirm(wnd,"Delete contact '" + c->m_name + "'?",context);
}
bool ok = delContact(item,wnd);
bool activeDel = hasEnabledCheckedItems(s_contactList,wnd);
Client::self()->setActive(YSTRING("abk_del"),activeDel,wnd);
return ok;
}
if (list == s_accountList) {
if (context)
return showConfirm(wnd,"Delete account '" + item + "'?",context);
return delAccount(item,wnd);
}
if (list == s_logList) {
if (context)
return showConfirm(wnd,"Delete the selected call log?",context);
bool ok = callLogDelete(item);
bool activeDel = hasEnabledCheckedItems(s_logList,wnd);
Client::self()->setActive(YSTRING("log_del"),activeDel,wnd);
return ok;
}
if (list == ClientContact::s_dockedChatWidget) {
if (wnd && wnd->id() == ClientContact::s_mucsWnd) {
MucRoom* room = m_accounts->findRoomByMember(item);
if (room && room->ownMember(item)) {
if (context) {
// Request confirmation if there is an opened private chat
ObjList* o = room->resources().skipNull();
for (; o; o = o->skipNext()) {
MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
if (room->hasChat(m->toString())) {
String text;
text << "You have active chat in room " << room->uri();
text << ".\r\nDo you want to proceed?";
return showConfirm(wnd,text,context);
}
}
}
logCloseMucSessions(room);
if (room->local() || room->remote()) {
clearRoom(room);
if (room->account() && room->account()->resource().online())
updateChatRoomsContactList(true,0,room);
}
else
TelEngine::destruct(room);
}
else if (room) {
MucRoomMember* m = room->findMemberById(item);
if (m)
logCloseSession(room,false,m->m_name);
Client::self()->delTableRow(list,item,wnd);
}
return true;
}
if (wnd && wnd->id() == ClientContact::s_dockedChatWnd) {
if (!s_changingDockedChat)
logCloseSession(m_accounts->findContact(item));
Client::self()->delTableRow(ClientContact::s_dockedChatWidget,item,wnd);
return true;
}
}
// Remove table row
return Client::self()->delTableRow(list,item,wnd);
}
// Handle list/table checked items deletion
bool DefaultLogic::deleteCheckedItems(const String& list, Window* wnd, bool confirm)
{
if (!(Client::valid() && list))
return false;
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::deleteCheckedItems(%s,%p,%u)",
list.c_str(),wnd,confirm);
ObjList* checked = getEnabledCheckedItems(list,wnd);
if (!checked)
return true;
String context;
if (confirm)
context << "deletecheckeditems:" << list;
bool ok = true;
while (true) {
// Handle known lists
if (list == s_contactList) {
for (ObjList* o = checked->skipNull(); o;) {
if (isLocalContact(static_cast<String*>(o->get()),m_accounts))
o = o->skipNext();
else {
o->remove();
o = o->skipNull();
}
}
if (!checked->skipNull())
break;
if (context) {
ok = showConfirm(wnd,"Delete selected contact(s)?",context);
break;
}
for (ObjList* o = checked->skipNull(); o; o = o->skipNext())
delContact(o->get()->toString(),wnd);
bool activeDel = hasEnabledCheckedItems(s_contactList,wnd);
Client::self()->setActive(YSTRING("abk_del"),activeDel,wnd);
break;
}
if (list == s_logList) {
if (context) {
ok = showConfirm(wnd,"Delete the selected call log item(s)?",context);
break;
}
for (ObjList* o = checked->skipNull(); o; o = o->skipNext())
callLogDelete(o->get()->toString());
bool activeDel = hasEnabledCheckedItems(s_logList,wnd);
Client::self()->setActive(YSTRING("log_del"),activeDel,wnd);
break;
}
// Remove table rows
for (ObjList* o = checked->skipNull(); o; o = o->skipNext())
Client::self()->delTableRow(list,o->get()->toString(),wnd);
break;
}
TelEngine::destruct(checked);
return ok;
}
// Handle list/table selection deletion
bool DefaultLogic::deleteSelectedItem(const String& action, Window* wnd, bool checked)
{
if (!Client::valid())
return false;
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::deleteSelectedItem(%s,%p) wnd=%s",
action.c_str(),wnd,wnd ? wnd->id().c_str() : "");
// Check for a confirmation text
int pos = action.find(":");
String list;
if (pos > 0)
list = action.substr(0,pos);
else if (pos < 0)
list = action;
if (!list)
return false;
if (!checked) {
String item;
Client::self()->getSelect(list,item,wnd);
return item && deleteItem(list,item,wnd,pos > 0);
}
if (hasEnabledCheckedItems(list,wnd))
return deleteCheckedItems(list,wnd,pos > 0);
return false;
}
// Handle text changed notification
bool DefaultLogic::handleTextChanged(NamedList* params, Window* wnd)
{
if (!(params && wnd))
return false;
const String& sender = (*params)[YSTRING("sender")];
if (!sender)
return false;
// Username changes in contact add/edit
bool isContact = wnd->id().startsWith("contactedit_");
if (isContact || wnd->id().startsWith("chatroomedit_")) {
if (!Client::valid())
return false;
const String& text = (*params)["text"];
if (isContact) {
if (!wnd->context() &&
checkUriTextChanged(wnd,sender,text,YSTRING("username"),YSTRING("domain")))
return true;
}
else if (checkUriTextChanged(wnd,sender,text,YSTRING("room_room"),YSTRING("room_server")))
return true;
return false;
}
// Search contact
if (sender == "search_contact") {
updateFilter(s_contactList,wnd,(*params)["text"],"name","number/uri");
return true;
}
// Editing started on the callto input, clear the callto_hing
if (sender == s_calltoList) {
Client::self()->setText(YSTRING("callto_hint"),YSTRING(""),false,wnd);
return true;
}
// Conf/transfer targets
bool conf = sender.startsWith("conf_add_target:");
if (conf || sender.startsWith("transfer_start_target:")) {
int l = conf ? 16 : 22;
int pos = sender.find(":",l + 1);
if (pos > 0) {
String chan = sender.substr(l,pos - l);
const char* suffix = conf ? "_conf_target" : "trans_target";
s_generic.setParam(chan + suffix,(*params)["text"]);
}
return true;
}
// Chat input changes
if (Client::valid() && Client::self()->getBoolOpt(Client::OptNotifyChatState)) {
ClientContact* c = 0;
MucRoom* room = 0;
String id;
if (sender == ClientContact::s_chatInput)
c = m_accounts->findContact(wnd->context());
else
getPrefixedContact(sender,ClientContact::s_chatInput,id,m_accounts,&c,&room);
MucRoomMember* m = (!c && room) ? room->findMemberById(id) : 0;
if (c || m) {
String* text = params->getParam(YSTRING("text"));
String tmp;
if (!text) {
text = &tmp;
if (c)
c->getChatInput(tmp);
else
room->getChatInput(id,tmp);
}
ContactChatNotify::update(c,room,m,text->null());
return true;
}
}
return false;
}
// Handle file transfer actions
bool DefaultLogic::handleFileTransferAction(const String& name, Window* wnd,
NamedList* params)
{
if (!Client::valid())
return false;
ClientContact* c = 0;
String file;
// Close file transfer
if (name.startsWith("fileprogress_close:",false)) {
String id = name.substr(19);
if (id && !m_ftManager->cancelFileTransfer(id))
FtManager::dropFileTransferItem(id);
return true;
}
// Destination chosen for file receive
if (name.startsWith(s_fileOpenRecvPrefix,false)) {
file = params ? params->getValue(YSTRING("file")) : "";
if (!file)
return true;
String id = name.substr(s_fileOpenRecvPrefix.length());
NamedList item("");
Client::self()->getTableRow(YSTRING("messages"),id,&item,wnd);
const String& chan = item[YSTRING("targetid")];
if (chan) {
// Add file transfer item
NamedList p(chan);
String buf;
const String& account = item[YSTRING("account")];
const String& contact = item[YSTRING("contact")];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
ClientContact* c = a ? a->findContactByUri(contact) : 0;
if (c)
buildContactName(buf,*c);
else
buf = contact;
FtManager::addFileTransferItem(chan,false,account,contact,String::empty(),
buf,file,chan);
// Remove the file
File::remove(file);
// Attach the consumer
Message m("chan.masquerade");
m.addParam("message","chan.attach");
m.addParam("id",chan);
m.addParam("consumer","filetransfer/receive/" + file);
m.copyParams(item);
m.addParam("autoclose",String::boolText(false));
m.addParam("notify",chan);
m.addParam("notify_progress",String::boolText(true));
Engine::dispatch(m);
// Answer the call
Message* anm = new Message("chan.masquerade");
anm->addParam("message","call.answered");
anm->addParam("id",chan);
Engine::enqueue(anm);
}
// Remove notification
Client::self()->delTableRow(YSTRING("messages"),id,wnd);
// Update/save last dir
s_lastFileDir = params->getValue(YSTRING("dir"));
Client::s_settings.setValue("filetransfer","dir",s_lastFileDir);
return true;
}
// Send file
if (name == s_fileSend)
c = getContactFromParamContext(m_accounts,params,s_chatContactList,wnd);
else if (name.startsWith("send_file:",false))
c = m_accounts->findContact(name.substr(10));
else if (name.startsWith(s_fileOpenSendPrefix,false)) {
// Choose file dialog action (params ? ok : cancel)
file = params ? params->getValue(YSTRING("file")) : "";
if (!file)
return true;
// Update/save last dir and filter
s_lastFileDir = params->getValue(YSTRING("dir"));
s_lastFileFilter = params->getValue(YSTRING("filter"));
Client::s_settings.setValue("filetransfer","dir",s_lastFileDir);
Client::s_settings.setValue("filetransfer","filter",s_lastFileFilter);
// Retrieve the contact
c = m_accounts->findContact(name.substr(s_fileOpenSendPrefix.length()));
}
else
return false;
if (!c)
return false;
if (!file)
return chooseFileTransfer(true,s_fileOpenSendPrefix + c->toString(),wnd);
ClientResource* res = c->findFileTransferResource();
Message m("call.execute");
m.addParam("callto","filetransfer/send/" + file);
String tmp;
Client::getLastNameInPath(tmp,file);
m.addParam("remote_file",tmp,false);
String direct("jingle/" + c->uri());
if (res)
direct << "/" << res->toString();
m.addParam("direct",direct);
m.addParam("line",c->accountName(),false);
m.addParam("getfilemd5",String::boolText(true));
m.addParam("getfileinfo",String::boolText(true));
m.addParam("notify_progress",String::boolText(true));
m.addParam("autoclose",String::boolText(false));
m.addParam("send_chunk_size","4096");
m.addParam("send_interval","10");
String notify(c->toString());
notify << String(file.hash()) << (int)Time::now();
m.addParam("notify",notify);
if (!Engine::dispatch(m)) {
String s;
s << "Failed to send '" << file << "' to " << c->uri();
s.append(m.getValue("error"),"\r\n");
showError(wnd,s);
return false;
}
NamedList p(notify);
String buf;
buildContactName(buf,*c);
FtManager::addFileTransferItem(notify,true,c->accountName(),c->uri(),String::empty(),
buf,file,m[YSTRING("id")]);
return true;
}
// Handle file transfer notifications
bool DefaultLogic::handleFileTransferNotify(Message& msg, bool& stopLogic)
{
const String& id = msg[YSTRING("targetid")];
if (!id)
return false;
if (Client::self()->postpone(msg,Client::TransferNotify)) {
stopLogic = true;
return true;
}
if (m_ftManager->handleFileTransferNotify(msg,id))
return true;
if (FtManager::isRunningNotify(msg))
FtManager::updateFtProgress(id,msg);
else
FtManager::updateFtFinished(id,msg,true);
return true;
}
// Handle user.data messages.
bool DefaultLogic::handleUserData(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
if (Client::self()->postpone(msg,Client::UserData)) {
stopLogic = true;
return false;
}
const String& data = msg[YSTRING("data")];
if (!data)
return false;
const String& account = msg[YSTRING("account")];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
if (!(a && a->resource().online()))
return false;
const String& oper = msg[YSTRING("operation")];
if (!oper)
return false;
bool ok = (oper == YSTRING("result"));
if (!ok && oper != YSTRING("error"))
return false;
const String& requested = msg[YSTRING("requested_operation")];
bool upd = (requested == YSTRING("update"));
if (ok) {
if (upd) {
// Update succeeded
return true;
}
// Handle request
if (data == YSTRING("chatrooms")) {
// Update MUC rooms
unsigned int n = msg.getIntValue(YSTRING("data.count"));
const NamedString* ns = 0;
NamedIterator iter(msg);
bool changed = false;
for (unsigned int i = 1; i <= n; i++) {
String prefix;
prefix << "data." << i;
const String& uri = msg[prefix];
if (!uri)
continue;
prefix << ".";
String id;
ClientContact::buildContactId(id,a->toString(),uri);
MucRoom* r = a->findRoom(id);
String pwd = msg[prefix + "password"];
if (pwd) {
Base64 b((void*)pwd.c_str(),pwd.length());
DataBlock tmp;
b.decode(tmp);
pwd.assign((const char*)tmp.data(),tmp.length());
}
const String& name = msg[prefix + "name"];
if (r) {
changed = setChangedString(r->m_name,name) || changed;
changed = setChangedString(r->m_password,pwd) || changed;
changed = setChangedParam(r->m_params,"autojoin",msg[prefix + "autojoin"]) ||
changed;
}
else {
changed = true;
r = new MucRoom(a,id,name,uri);
r->m_password = pwd;
r->setLocal(false);
}
r->setRemote(true);
// Copy other params
for (iter.reset(); 0 != (ns = iter.get());) {
if (!ns->name().startsWith(prefix))
continue;
String param = ns->name().substr(prefix.length());
if (param == YSTRING("group"))
continue;
changed = setChangedParam(r->m_params,param,*ns) || changed;
}
Debug(ClientDriver::self(),DebugAll,
"Account(%s) updated remote MUC room '%s' [%p]",
account.c_str(),r->uri().c_str(),a);
// Auto join
if (changed && r->m_params.getBoolValue("autojoin") &&
checkGoogleRoom(r->uri()))
joinRoom(r);
}
if (changed)
updateChatRoomsContactList(true,a);
// Merge local/remote rooms
bool saveRemote = false;
for (ObjList* o = a->mucs().skipNull(); o; o = o->skipNext()) {
MucRoom* r = static_cast<MucRoom*>(o->get());
if (r->local()) {
if (!r->remote()) {
r->setRemote(true);
saveRemote = true;
}
}
else if (r->remote()) {
r->setLocal(true);
saveContact(a->m_cfg,r);
}
}
if (saveRemote)
Engine::enqueue(a->userData(true,"chatrooms"));
}
}
else {
String error;
String reason = msg[YSTRING("error")];
if (reason) {
error << reason;
const String& res = msg["reason"];
if (res)
error << " (" << res << ")";
}
else
error << msg[YSTRING("reason")];
Debug(ClientDriver::self(),DebugNote,
"Account(%s) private data %s '%s' failed: %s",
account.c_str(),requested.c_str(),data.c_str(),error.c_str());
}
return true;
}
// Handle file.info messages.
bool DefaultLogic::handleFileInfo(Message& msg, bool& stopLogic)
{
static const String s_isFile = "isfile";
if (Client::isClientMsg(msg))
return false;
const String& oper = msg[YSTRING("operation")];
bool changed = false;
int rsp = 0;
if (oper == YSTRING("result"))
rsp = 1;
else if (oper == YSTRING("error"))
rsp = -1;
else if (oper == YSTRING("changed"))
changed = true;
else
return false;
if (Client::self()->postpone(msg,Client::FileInfo)) {
stopLogic = true;
return false;
}
RefPointer<PendingRequest> r;
if (rsp) {
const String& requestId = msg[YSTRING("id")];
if (!(requestId && PendingRequest::find(requestId,r)))
return false;
}
const String& account = msg[YSTRING("account")];
const String& cUri = msg[YSTRING("from")];
const String& inst = msg[YSTRING("from_instance")];
if (r && r->type() == PendingRequest::SharedQuery)
m_ftManager->handleFileInfoRsp(account,cUri,inst,oper,msg);
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
if (!(a && a->resource().online())) {
if (r) {
PendingRequest::remove(r->toString());
r = 0;
}
return false;
}
while (true) {
if (changed) {
handleFileSharedChanged(a,cUri,inst);
break;
}
if (!rsp)
break;
if (r->type() == PendingRequest::SharedQuery) {
ClientContact* c = cUri ? a->findContactByUri(cUri) : 0;
ClientResource* res = c ? c->findResource(inst) : 0;
if (!res) {
rsp = -1;
break;
}
Window* wShared = getContactShareWnd(false,c);
NamedList updSharedDirs("");
NamedList updSharedDirContent("");
String* path = msg.getParam(YSTRING("dir"));
bool oldShared = c->haveShared();
if (path) {
ClientDir* resDir = c->getShared(res->toString(),true);
ClientDir* dir = 0;
if (*path)
dir = resDir->addDirPath(*path);
else
dir = resDir;
if (!dir) {
rsp = -1;
break;
}
// Make sure the path is shown in dirs list
if (wShared)
sharedDirsAddUpdate(updSharedDirs,c,resDir,*path);
for (int i = 1; rsp > 0; i++) {
String prefix("item.");
prefix << i;
NamedString* ns = msg.getParam(prefix);
if (!ns)
break;
if (!*ns)
continue;
prefix << ".";
ClientFileItem* item = 0;
if (msg.getBoolValue(prefix + s_isFile)) {
NamedList p("");
copySubParams(p,msg,prefix,"file_",s_isFile);
item = new ClientFile(*ns,&p);
}
else
item = new ClientDir(*ns);
dir->addChild(item);
// Show item in dirs list
if (wShared && item->directory())
sharedDirsAddUpdate(updSharedDirs,c,resDir,*path,item->directory());
}
if (dir && !msg.getBoolValue(YSTRING("partial"))) {
dir->updated(true);
// Check selection
Window* w = getContactShareWnd(false,c);
if (w) {
String sel;
Client::self()->getSelect(s_fileSharedDirsList,sel,w);
if (sel) {
String tmp;
sharedBuildId(tmp,*resDir,*path);
if (tmp == sel)
sharedContentUpdate(c,resDir,*path,dir,w);
}
}
}
if (oldShared != c->haveShared()) {
enableChatActions(c,true,true,true);
showChatContactActions(*c);
}
}
else {
rsp = -1;
String* file = msg.getParam(YSTRING("file"));
if (file)
Debug(ClientDriver::self(),DebugStub,
"DefaultLogic::handleFileInfo() not implemented for file");
}
if (wShared) {
if (updSharedDirs.getParam(0))
Client::self()->updateTableRows(s_fileSharedDirsList,&updSharedDirs,
false,wShared);
if (updSharedDirContent.getParam(0))
Client::self()->updateTableRows(s_fileSharedDirsContent,&updSharedDirContent,
false,wShared);
}
break;
}
rsp = -1;
break;
}
if (r) {
if (rsp < 0 || !msg.getBoolValue(YSTRING("partial")))
PendingRequest::remove(r->toString());
r = 0;
}
return false;
}
// Show a generic notification
void DefaultLogic::notifyGenericError(const String& text, const String& account,
const String& contact, const char* title)
{
NamedList list("");
NamedList* upd = buildNotifArea(list,"generic",account,contact,title);
setGenericNotif(*upd);
upd->addParam("text",text);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&list);
}
// Show/hide no audio notification (chan==0: initial check)
void DefaultLogic::notifyNoAudio(bool show, bool micOk, bool speakerOk,
ClientChannel* chan)
{
if (!Client::valid())
return;
Window* w = Client::self()->getWindow(s_wndMain);
if (!show) {
String id;
buildNotifAreaId(id,"noaudio",String::empty());
Client::self()->delTableRow("messages",id,w);
return;
}
if (micOk && speakerOk)
return;
NamedList list("");
NamedList* upd = buildNotifArea(list,"noaudio",String::empty(),
String::empty(),"Audio failure");
String text;
if (chan) {
text << "Failed to open ";
if (!(micOk || speakerOk))
text << "audio";
else if (micOk)
text << "speaker";
else
text << "microphone";
text << ".\r\nPlease check your sound card";
}
else
return;
upd->addParam("text",text);
setGenericNotif(*upd);
Client::self()->updateTableRows("messages",&list,false,w);
NamedList p("");
const char* ok = String::boolText(show);
p.addParam("check:messages_show",ok);
p.addParam("show:frame_messages",ok);
Client::self()->setParams(&p,w);
}
// Utility used in DefaultLogic::updateChatRoomsContactList
// Build (un)load a chat room parameter
static void addChatRoomParam(NamedList& upd, bool load, MucRoom* room)
{
if (!(room && (!load || room->local() || room->remote())))
return;
NamedList* p = new NamedList(room->toString());
if (load)
fillChatContact(*p,*room,true,true,true);
upd.addParam(new NamedPointer(*p,p,load ? String::boolText(true) : ""));
}
// (Un)Load chat rooms
void DefaultLogic::updateChatRoomsContactList(bool load, ClientAccount* acc,
MucRoom* room)
{
if (!(Client::valid() && (acc || room)))
return;
NamedList upd("");
if (acc) {
for (ObjList* o = acc->mucs().skipNull(); o; o = o->skipNext())
addChatRoomParam(upd,load,static_cast<MucRoom*>(o->get()));
}
else
addChatRoomParam(upd,load,room);
Client::self()->updateTableRows(s_chatContactList,&upd,false);
}
// Join a MUC room. Create/show chat. Update its status
void DefaultLogic::joinRoom(MucRoom* room, bool force)
{
if (!room)
return;
if (!room->resource().offline()) {
if (force) {
room->m_params.setParam("internal.reconnect",String::boolText(true));
Engine::enqueue(room->buildJoin(false));
}
createRoomChat(*room);
return;
}
room->resource().m_name = room->m_params.getValue("nick");
if (!room->resource().m_name && room->account()) {
if (room->account()->contact())
room->resource().m_name = room->account()->contact()->uri().getUser();
if (!room->resource().m_name)
room->resource().m_name = room->account()->params().getValue(YSTRING("username"));
}
if (!checkGoogleRoom(room->uri()))
return;
bool hist = room->m_params.getBoolValue(YSTRING("history"),true);
unsigned int lastMinutes = 0;
if (hist)
lastMinutes = room->m_params.getIntValue(YSTRING("historylast"));
Message* m = room->buildJoin(true,hist,lastMinutes * 60);
room->resource().m_status = ClientResource::Connecting;
updateChatRoomsContactList(true,0,room);
createRoomChat(*room);
Engine::enqueue(m);
}
// Utility used in updateAccount
static void updAccDelOld(ClientAccount*& old, ClientLogic* logic)
{
if (!old)
return;
if (!old->resource().offline())
Engine::enqueue(userLogin(old,false));
logic->delAccount(old->toString(),0);
TelEngine::destruct(old);
}
// Add/set an account
bool DefaultLogic::updateAccount(const NamedList& account, bool save,
const String& replace, bool loaded)
{
DDebug(ClientDriver::self(),DebugAll,
"ClientLogic(%s)::updateAccount(%s) save=%u replace=%s loaded=%u",
toString().c_str(),account.c_str(),save,replace.safe(),loaded);
ClientAccount* repl = replace ? m_accounts->findAccount(replace,true) : 0;
ClientAccount* acc = m_accounts->findAccount(account,true);
// This should never happen
if (repl && acc && acc != repl) {
TelEngine::destruct(repl);
TelEngine::destruct(acc);
Debug(ClientDriver::self(),DebugWarn,
"Attempt to replace an existing account with another account");
return false;
}
if (repl) {
TelEngine::destruct(acc);
acc = repl;
}
String oldDataDir = acc ? acc->dataDir() : String::empty();
bool changed = false;
// Update account
// Postpone old account deletion: let the new account take files
// from the old one's data directory
ClientAccount* old = 0;
if (acc) {
if (acc->toString() != account) {
// Account id changed:
// Disconnect the account, remove it and add a new one
old = acc;
acc = 0;
}
else {
// Compare account parameters
changed = !(sameParams(acc->params(),account,s_accParams) &&
sameParams(acc->params(),account,s_accBoolParams) &&
sameParams(acc->params(),account,s_accProtoParams) &&
sameParams(acc->params(),account,s_accProtoParamsSel));
if (changed)
acc->m_params.copyParams(account);
}
}
if (!acc) {
String id;
// Adjust loaded account id to internally generated id
if (loaded) {
URI uri(account);
if (!(uri.getProtocol() && uri.getUser() && uri.getHost())) {
const String& proto = account[YSTRING("protocol")];
const String& user = account[YSTRING("username")];
const char* host = account.getValue(YSTRING("domain"),account.getValue(YSTRING("server")));
if (proto && user && host)
buildAccountId(id,proto,user,host);
else {
updAccDelOld(old,this);
Debug(ClientDriver::self(),DebugNote,
"Ignoring loaded account '%s' proto=%s user=%s host=%s",
account.c_str(),proto.c_str(),user.c_str(),host);
return false;
}
}
}
if (!id)
acc = new ClientAccount(account);
else {
NamedList p(account);
if (id != account) {
Debug(ClientDriver::self(),DebugInfo,
"Renaming loaded account '%s' to '%s'",
account.c_str(),id.c_str());
p.assign(id);
}
acc = new ClientAccount(p);
if (id != account)
acc->m_params.setParam("old_id",account.c_str());
}
if (loaded && !acc->params().getParam(YSTRING("savepassword")))
acc->m_params.setParam("savepassword",
String::boolText(0 != acc->params().getParam(YSTRING("password"))));
if (!m_accounts->appendAccount(acc)) {
updAccDelOld(old,this);
Debug(ClientDriver::self(),DebugNote,
"Failed to append duplicate account '%s'",acc->toString().c_str());
TelEngine::destruct(acc);
return false;
}
changed = true;
}
if (!changed) {
updAccDelOld(old,this);
TelEngine::destruct(acc);
return true;
}
// Clear pending params
acc->m_params.clearParam(YSTRING("internal.status"),'.');
// (Re)set account own contact
setAccountContact(acc);
// Update account list
NamedList p("");
acc->fillItemParams(p);
p.addParam("check:enabled",String::boolText(acc->startup()));
p.addParam("status_image",resStatusImage(acc->resource().m_status),false);
Client::self()->updateTableRow(s_accountList,acc->toString(),&p);
// Make sure the account is selected in accounts list
Client::self()->setSelect(s_accountList,acc->toString());
// Update telephony account selector(s)
updateTelAccList(acc->startup(),acc);
// Reset selection if loaded: it will be set in setAdvancedMode() if appropriate
if (loaded)
Client::self()->setSelect(s_account,s_notSelected);
setAdvancedMode();
// (Dis)connect account
if (acc->resource().offline()) {
if (!loaded && acc->startup())
setAccountStatus(m_accounts,acc);
}
else {
Engine::enqueue(userLogin(acc,false));
acc->m_params.setParam("internal.reconnect",String::boolText(true));
}
// Clear saved rooms
updateChatRoomsContactList(false,acc);
acc->clearRooms(true,false);
acc->m_cfg.assign("");
acc->m_cfg.clearSection();
// Update dir data
acc->m_params.setParam("datadirectory",oldDataDir);
String error;
if (acc->setupDataDir(&error)) {
acc->loadDataDirCfg();
acc->loadContacts();
}
else
notifyGenericError(error,acc->toString());
// Save the account
if (save)
acc->save(true,acc->params().getBoolValue(YSTRING("savepassword")));
TelEngine::destruct(acc);
updAccDelOld(old,this);
return true;
}
// Add/edit an account
bool DefaultLogic::internalEditAccount(bool newAcc, const String* account, NamedList* params,
Window* wnd)
{
if (!Client::valid() || Client::self()->getVisible(s_wndAccount))
return false;
NamedList dummy("");
if (!params)
params = &dummy;
// Make sure we reset all controls in window
params->setParam("select:" + s_accProviders,s_notSelected);
String proto;
ClientAccount* a = 0;
if (newAcc) {
proto = Client::s_settings.getValue("client","acc_protocol","sip");
// Check if the protocol is valid. Retrieve the first one if invalid
s_protocolsMutex.lock();
if (proto && !s_protocols.find(proto))
proto = "";
if (!proto) {
ObjList* o = s_protocols.skipNull();
if (o)
proto = o->get()->toString();
}
s_protocolsMutex.unlock();
}
else {
if (TelEngine::null(account))
a = selectedAccount(*m_accounts,wnd);
else
a = m_accounts->findAccount(*account);
if (!a)
return false;
proto = a->protocol();
}
const String& acc = a ? a->toString() : String::empty();
// Protocol combo and specific widget (page) data
bool adv = Client::s_settings.getBoolValue("client","acc_showadvanced",true);
params->setParam("check:acc_showadvanced",String::boolText(adv));
selectProtocolSpec(*params,proto,adv,s_accProtocol);
// Save password ?
bool save = false;
if (a)
save = a->params().getBoolValue(YSTRING("savepassword"));
params->setParam("check:acc_savepassword",String::boolText(save));
// Reset all protocol specific data
updateProtocolList(0,String::empty(),0,params);
if (a)
updateProtocolSpec(*params,proto,true,a->params());
params->setParam("title",newAcc ? "Add account" : ("Edit account: " + acc).c_str());
params->setParam("context",acc);
return Client::openPopup(s_wndAccount,params);
}
// Utility used in handleDialogAction() to retrieve the room from context if input
// is valid
static inline MucRoom* getInput(ClientAccountList* list, const String& id, Window* w,
String& input, bool emptyOk = false)
{
if (!(list && id))
return 0;
Client::self()->getText(YSTRING("inputdialog_input"),input,false,w);
return (emptyOk || input) ? list->findRoom(id) : 0;
}
// Handle dialog actions. Return true if handled
bool DefaultLogic::handleDialogAction(const String& name, bool& retVal, Window* wnd)
{
String n(name);
if (!n.startSkip("dialog:",false))
return false;
int pos = n.find(":");
if (pos < 0)
return false;
String dlg = n.substr(0,pos);
String ctrl = n.substr(pos + 1);
DDebug(ClientDriver::self(),DebugAll,
"DefaultLogic handleDialogAction(%s) dlg=%s action=%s wnd=%s",
name.c_str(),dlg.c_str(),ctrl.c_str(),wnd ? wnd->id().c_str() : "");
if (ctrl == "button_hide") {
retVal = true;
return true;
}
if (ctrl != YSTRING("ok"))
return false;
String context;
if (wnd && Client::valid())
Client::self()->getProperty(dlg,YSTRING("_yate_context"),context,wnd);
// Handle OK
if (dlg == s_mucChgSubject) {
// Accept MUC room subject change
String subject;
MucRoom* room = getInput(m_accounts,context,wnd,subject,true);
retVal = room && room->canChangeSubject();
if (retVal) {
Message* m = room->buildMucRoom("setsubject");
m->addParam("subject",subject);
retVal = Engine::enqueue(m);
}
}
else if (dlg == s_mucChgNick) {
// Accept MUC room nick change
String nick;
MucRoom* room = getInput(m_accounts,context,wnd,nick);
retVal = room && room->resource().online();
if (retVal && nick != room->resource().m_name) {
if (!isGoogleMucDomain(room->uri().getHost())) {
Message* m = room->buildMucRoom("setnick");
m->addParam("nick",nick);
retVal = Engine::enqueue(m);
}
else {
// Google MUC: send unavailable first
Message* m = room->buildJoin(false);
if (Engine::enqueue(m)) {
m = room->buildJoin(true);
m->setParam("nick",nick);
retVal = Engine::enqueue(m);
}
}
}
}
else if (dlg == s_mucInviteAdd) {
// Add contact to muc invite
String contact;
Client::self()->getText(YSTRING("inputdialog_input"),contact,false,wnd);
String user, domain;
splitContact(contact,user,domain);
retVal = user && domain;
if (retVal && Client::valid() &&
!Client::self()->getTableRow(s_inviteContacts,contact,0,wnd)) {
NamedList row("");
row.addParam("name",contact);
row.addParam("contact",contact);
row.addParam("check:name",String::boolText(true));
row.addParam("name_image",Client::s_skinPath + "addcontact.png");
Client::self()->addTableRow(s_inviteContacts,contact,&row,false,wnd);
}
}
else
retVal = context && Client::self()->action(wnd,context);
return true;
}
// Handle chat and contact related actions. Return true if handled
bool DefaultLogic::handleChatContactAction(const String& name, Window* wnd)
{
ClientContact* c = 0;
MucRoom* room = 0;
String id;
// Send chat action from single chat window, docked chat or MUC room
bool ok = getPrefixedContact(name,s_chatSend,id,m_accounts,&c,&room);
if (ok || name == s_chatSend) {
// Single chat window
if (!ok && wnd && wnd->context())
c = m_accounts->findContact(wnd->context());
if (c) {
DDebug(ClientDriver::self(),DebugAll,
"DefaultLogic sending chat for contact=%s",c->toString().c_str());
String text;
c->getChatInput(text);
if ((text || Client::self()->getBoolOpt(Client::OptSendEmptyChat)) &&
c->sendChat(text)) {
unsigned int time = Time::secNow();
NamedList* tmp = buildChatParams(text,"me",time);
c->setChatProperty("history","_yate_tempitemreplace",String(false));
c->addChatHistory("chat_out",tmp);
c->setChatProperty("history","_yate_tempitemreplace",String(true));
c->setChatInput();
if (text)
logChat(c,time,true,false,text);
}
}
else if (room) {
MucRoomMember* m = id ? room->findMemberById(id) : 0;
if (!m)
return false;
DDebug(ClientDriver::self(),DebugAll,
"DefaultLogic sending MUC chat room=%s nick=%s",
room->uri().c_str(),m->m_name.c_str());
String text;
room->getChatInput(id,text);
bool ok = text || Client::self()->getBoolOpt(Client::OptSendEmptyChat);
if (room->ownMember(m))
ok = ok && room->sendChat(text,String::empty(),"groupchat");
else
ok = ok && room->sendChat(text,m->m_name);
if (ok) {
unsigned int time = Time::secNow();
NamedList* tmp = buildChatParams(text,"me",time);
room->setChatProperty(id,"history","_yate_tempitemreplace",String(false));
room->addChatHistory(id,"chat_out",tmp);
room->setChatProperty(id,"history","_yate_tempitemreplace",String(true));
room->setChatInput(id);
if (text)
logChat(room,time,true,false,text,room->ownMember(m),m->m_name);
}
}
else
return false;
return true;
}
// Show contact chat
if (name == s_chat || name == s_chatContactList) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
if (!c)
return false;
MucRoom* r = c->mucRoom();
if (!r) {
if (!c->hasChat()) {
c->createChatWindow();
NamedList p("");
fillChatContact(p,*c,true,true);
fillChatContactShareStatus(p,*c,false,true);
ClientResource* res = c->status();
c->updateChatWindow(p,"Chat [" + c->m_name + "]",
resStatusImage(res ? res->m_status : ClientResource::Offline));
}
c->showChat(true,true);
}
else if (checkGoogleRoom(r->uri(),wnd))
joinRoom(r);
return true;
}
// Call chat contact
if (name == s_chatCall) {
ClientContact* c = selectedChatContact(*m_accounts,wnd,false);
if (!c)
return false;
ClientResource* res = c->findAudioResource();
if (!res)
return false;
NamedList p("");
p.addParam("line",c->accountName(),false);
p.addParam("account",c->accountName(),false);
p.addParam("target",c->uri());
p.addParam("instance",res->toString());
if (c->account())
p.addParam("protocol",c->account()->protocol(),false);
return callStart(p);
}
// Show chat contact log
if (name == s_chatShowLog) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
return logShow(c);
}
// Edit chat contact
if (name == s_chatEdit) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
return c && showContactEdit(*m_accounts,false,c);
}
if (getPrefixedContact(name,s_chatEdit,id,m_accounts,&c,&room) && c) {
bool ok = showContactEdit(*m_accounts,false,c);
if (ok && wnd) {
// Hide contact info window
Window* w = getContactInfoEditWnd(false,false,c);
if (wnd == w)
Client::self()->closeWindow(wnd->id());
}
return ok;
}
// Add chat contact
if (name == s_chatNew)
return showContactEdit(*m_accounts,false);
if (name == s_chatRoomNew) {
s_mucWizard->start(true);
return true;
}
// Remove chat contact
if (name == s_chatDel)
return deleteSelectedItem(s_chatContactList + ":",wnd);
// Show chat contact info
if (name == s_chatInfo) {
ClientContact* c = selectedChatContact(*m_accounts,wnd,false);
return updateContactInfo(c,true,true);
}
// Subscription management
bool sub = (name == s_chatSub);
bool unsubd = !sub && (name == s_chatUnsubd);
if (sub || unsubd || name == s_chatUnsub) {
ClientContact* c = selectedChatContact(*m_accounts,wnd,false);
if (!c)
return false;
if (!unsubd)
Engine::enqueue(Client::buildSubscribe(true,sub,c->accountName(),c->uri()));
else
Engine::enqueue(Client::buildSubscribe(false,false,c->accountName(),c->uri()));
return true;
}
// Add group in contact edit/add window
if (name == "contactedit_addgroup") {
if (!(Client::valid() && wnd))
return false;
String grp;
Client::self()->getText(YSTRING("editgroup"),grp,false,wnd);
if (!grp)
return false;
NamedList upd("");
NamedList* p = new NamedList(grp);
p->addParam("group",grp);
p->addParam("check:group",String::boolText(true));
upd.addParam(new NamedPointer(grp,p,String::boolText(true)));
if (Client::self()->updateTableRows(YSTRING("groups"),&upd,false,wnd))
Client::self()->setText(YSTRING("editgroup"),String::empty(),false,wnd);
return true;
}
// Invite chat contacts, create room
ok = getPrefixedContact(name,s_mucInvite,id,m_accounts,&c,0);
if (ok || name == s_mucInvite) {
if (!ok && wnd && wnd->context())
c = m_accounts->findContact(wnd->context());
if (!c)
return false;
showMucInvite(*c,m_accounts);
return true;
}
// Store contact
if (getPrefixedContact(name,s_storeContact,id,m_accounts,0,&room)) {
if (room)
updateChatRoomsContactList(room->local() || room->remote(),0,room);
return storeContact(room);
}
return false;
}
// Handle chat contact edit ok button press. Return true if handled
bool DefaultLogic::handleChatContactEditOk(const String& name, Window* wnd)
{
static const String s_cceokname = "contactedit_ok";
if (name != s_cceokname)
return false;
if (!(Client::valid() && wnd))
return true;
String contact;
ClientAccount* a = 0;
if (wnd->context()) {
// Edit
ClientContact* c = m_accounts->findContact(wnd->context());
if (c) {
a = c->account();
contact = c->uri();
}
if (!a) {
// Try to retrieve from data
String account;
Client::self()->getText(YSTRING("chatcontact_account"),account,false,wnd);
a = m_accounts->findAccount(account);
if (!a) {
showError(wnd,"Account does not exists");
return true;
}
Client::self()->getText(YSTRING("chatcontact_uri"),contact,false,wnd);
}
}
else {
a = selectedAccount(*m_accounts,wnd,s_chatAccount);
if (!a) {
showAccSelect(wnd);
return true;
}
String user, domain;
Client::self()->getText(YSTRING("username"),user,false,wnd);
Client::self()->getText(YSTRING("domain"),domain,false,wnd);
if (!checkUri(wnd,user,domain,false))
return true;
contact << user << "@" << domain;
// Check unique
if (a->findRoomByUri(contact)) {
showRoomDupError(wnd);
return true;
}
}
if (!a->resource().online()) {
showError(wnd,"Selected account is offline");
return true;
}
String cname;
Client::self()->getText(YSTRING("name"),cname,false,wnd);
bool reqSub = false;
if (!wnd->context())
Client::self()->getCheck(YSTRING("request_subscribe"),reqSub,wnd);
NamedList p("");
Client::self()->getOptions(YSTRING("groups"),&p,wnd);
Message* m = Client::buildUserRoster(true,a->toString(),contact);
m->addParam("name",cname,false);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!(ns && ns->name()))
continue;
NamedList pp("");
Client::self()->getTableRow(YSTRING("groups"),ns->name(),&pp,wnd);
if (pp.getBoolValue(YSTRING("check:group")))
m->addParam("group",ns->name(),false);
}
Engine::enqueue(m);
if (reqSub)
Engine::enqueue(Client::buildSubscribe(true,true,a->toString(),contact));
Client::self()->setVisible(wnd->id(),false);
return true;
}
// Handle chat room contact edit ok button press. Return true if handled
bool DefaultLogic::handleChatRoomEditOk(const String& name, Window* wnd)
{
static const String s_creokname = "chatroomedit_ok";
if (name != s_creokname)
return false;
if (!(Client::valid() && wnd))
return false;
ClientAccount* a = selectedAccount(*m_accounts,wnd,s_chatAccount);
if (!a)
return showAccSelect(wnd);
// Retrieve user/domain
String user, domain;
Client::self()->getText(YSTRING("room_room"),user,false,wnd);
Client::self()->getText(YSTRING("room_server"),domain,false,wnd);
if (!checkUri(wnd,user,domain,true))
return false;
// Check if changed
String id;
String contact(user + "@" + domain);
ClientContact::buildContactId(id,a->toString(),contact);
MucRoom* room = a->findRoom(id);
if (wnd->context()) {
MucRoom* e = 0;
if (wnd->context() != id)
e = m_accounts->findRoom(wnd->context());
if (e) {
// Reset non temporary old one
// Delete it if don't have chat displayed
if (e->local() || e->remote()) {
e->setLocal(false);
e->setRemote(false);
updateChatRoomsContactList(false,0,e);
storeContact(e);
}
if (!e->hasChat(e->resource().toString()))
TelEngine::destruct(e);
}
}
room = 0;
bool dataChanged = false;
bool changed = getRoom(wnd,a,true,!wnd->context(),room,dataChanged);
if (!room)
return false;
updateChatRoomsContactList(true,0,room);
if (dataChanged)
storeContact(room);
if (room->m_params.getBoolValue(YSTRING("autojoin")))
joinRoom(room,changed);
Client::self()->setVisible(wnd->id(),false);
return true;
}
// Handle actions from MUCS window. Return true if handled
bool DefaultLogic::handleMucsAction(const String& name, Window* wnd, NamedList* params)
{
XDebug(ClientDriver::self(),DebugAll,"DefaultLogic::handleMucsAction(%s)",
name.c_str());
MucRoom* room = 0;
String id;
if (getPrefixedContact(name,s_mucMembers,id,m_accounts,0,&room) ||
getPrefixedContact(name,s_mucPrivChat,id,m_accounts,0,&room)) {
// Handle item pressed in members list or show private chat
MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
if (member && !room->ownMember(member) && room->canChatPrivate())
createRoomChat(*room,member,true);
return member != 0;
}
if (getPrefixedContact(name,s_mucChgSubject,id,m_accounts,0,&room)) {
// Change room subject
if (room && room->ownMember(id) && room->canChangeSubject()) {
String text;
text << "Change room '" << room->uri() << "' subject";
showInput(wnd,s_mucChgSubject,text,room->toString(),"Change room subject");
}
return true;
}
if (getPrefixedContact(name,s_mucChgNick,id,m_accounts,0,&room)) {
// Change room nickname
if (room && room->ownMember(id)) {
String text;
text << "Change nickname in room '" << room->uri() << "'";
showInput(wnd,s_mucChgNick,text,room->toString(),"Change nickname");
}
return true;
}
if (getPrefixedContact(name,s_mucInvite,id,m_accounts,0,&room)) {
// Invite contacts to conference
if (!room)
return false;
showMucInvite(*room,m_accounts);
return true;
}
if (getPrefixedContact(name,s_mucRoomShowLog,id,m_accounts,0,&room)) {
// Show MUC room log
if (!room)
return false;
logShow(room,true);
return true;
}
if (getPrefixedContact(name,s_mucMemberShowLog,id,m_accounts,0,&room)) {
// Show MUC room member log
MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
if (!member)
return false;
logShow(room,room->ownMember(member),member->m_name);
return true;
}
bool kick = getPrefixedContact(name,s_mucKick,id,m_accounts,0,&room);
if (kick || getPrefixedContact(name,s_mucBan,id,m_accounts,0,&room)) {
MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
if (!member || room->ownMember(member))
return false;
if (kick) {
if (room->canKick(member)) {
// TODO: implement reason input from user
Message* m = room->buildMucRoom("kick");
m->addParam("nick",member->m_name);
Engine::enqueue(m);
}
}
else if (room->canBan(member) && member->m_uri) {
Message* m = room->buildMucRoom("ban");
m->addParam("contact",member->m_uri);
Engine::enqueue(m);
}
return true;
}
// Save/edit chat room contact
if (getPrefixedContact(name,s_mucSave,id,m_accounts,0,&room))
return room && showContactEdit(*m_accounts,true,room);
// Join MUC room
if (getPrefixedContact(name,s_mucJoin,id,m_accounts,0,&room)) {
joinRoom(room,params && params->getBoolValue(YSTRING("force")));
return room != 0;
}
// Add contact to muc invite list
if (name == s_mucInviteAdd) {
showInput(wnd,name,"Invite friend to conference",name,"Invite friend");
return true;
}
return false;
}
// Handle 'ok' in MUC invite window
bool DefaultLogic::handleMucInviteOk(Window* w)
{
if (!(w && Client::valid() ))
return false;
String account;
Client::self()->getText("invite_account",account,false,w);
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc) {
showError(w,"Account not found!");
return false;
}
String room;
Client::self()->getText("invite_room",room,false,w);
MucRoom* r = 0;
if (room) {
r = acc->findRoomByUri(room);
if (!r) {
showError(w,"MUC room not found!");
return false;
}
}
else {
String guid;
Client::generateGuid(guid,account);
String uri = "private-chat-" + guid;
uri << "@" << (isGmailAccount(acc) ? s_googleMucDomain : "conference.jabber.org");
String id;
ClientContact::buildContactId(id,account,uri);
r = acc->findRoom(id);
if (!r)
r = new MucRoom(acc,id,"",uri);
}
String text;
Client::self()->getText(YSTRING("invite_text"),text,false,w);
ObjList chosen;
getSelectedContacts(chosen,s_inviteContacts,w,YSTRING("name"));
bool inviteNow = (room || r->resource().online());
unsigned int count = 0;
r->m_params.clearParam(YSTRING("internal.invite"),'.');
for (ObjList* o = chosen.skipNull(); o; o = o->skipNext()) {
NamedList* nl = static_cast<NamedList*>(o->get());
const String& uri = (*nl)[YSTRING("contact")];
if (inviteNow)
Engine::enqueue(buildMucRoom("invite",account,room,text,uri));
else {
count++;
r->m_params.addParam("internal.invite.contact",uri);
}
}
if (!inviteNow) {
if (count) {
r->m_params.addParam("internal.invite.count",String(count));
r->m_params.addParam("internal.invite.text",text,false);
}
joinRoom(r);
}
Client::self()->setVisible(w->id(),false);
return true;
}
// Handle select from MUCS window. Return true if handled
bool DefaultLogic::handleMucsSelect(const String& name, const String& item, Window* wnd,
const String& text)
{
MucRoom* room = 0;
String id;
if (getPrefixedContact(name,s_mucMembers,id,m_accounts,0,&room)) {
// Handle selection changes in members list
MucRoomMember* member = (room && item) ? room->findMemberById(item) : 0;
if (!room)
return false;
// Enable/disable actions
NamedList p("");
enableMucActions(p,*room,member,false);
room->updateChatWindow(room->resource().toString(),p);
return true;
}
return false;
}
// Handle resource.notify messages from MUC rooms
// The account was already checked
bool DefaultLogic::handleMucResNotify(Message& msg, ClientAccount* acc, const String& contact,
const String& instance, const String& operation)
{
if (!acc)
return false;
MucRoom* room = acc->findRoomByUri(contact);
if (!room)
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) handle MUC notify account=%s contact=%s instance=%s operation=%s",
name().c_str(),acc->toString().c_str(),contact.c_str(),instance.safe(),
operation.c_str());
MucRoomMember* member = 0;
const String& mucContact = msg[YSTRING("muc.contact")];
const String& mucInst = msg[YSTRING("muc.contactinstance")];
String nick;
if (mucContact && mucInst) {
member = room->findMember(mucContact,mucInst);
if (member && room->ownMember(member))
nick = instance;
}
if (!member)
member = instance ? room->findMember(instance) : 0;
if (operation == YSTRING("error")) {
if (instance && !room->ownMember(member))
return false;
if (room->resource().m_status == ClientResource::Connecting) {
// Connection refused
String text("Failed to join room");
text.append(msg.getValue(YSTRING("reason"),msg.getValue(YSTRING("error"))),": ");
addChatNotify(*room,text,msg.msgTime().sec());
room->resource().m_status = ClientResource::Offline;
updateMucRoomMember(*room,room->resource());
room->m_params.clearParam(YSTRING("internal.invite"),'.');
room->m_params.clearParam(YSTRING("internal.reconnect"));
}
return true;
}
if (!instance)
return false;
bool online = (operation == YSTRING("online"));
if (!online && operation != YSTRING("offline"))
return false;
// Get user status notifications
ObjList* list = String(msg.getParam(YSTRING("muc.userstatus"))).split(',');
bool newRoom = 0 != list->find(YSTRING("newroom"));
bool ownUser = 0 != list->find(YSTRING("ownuser"));
bool userKicked = !online && list->find(YSTRING("userkicked"));
bool userBanned = !online && list->find(YSTRING("userbanned"));
if (!ownUser && list->find(YSTRING("nickchanged")))
nick = msg.getParam(YSTRING("muc.nick"));
TelEngine::destruct(list);
// Retrieve the member
if (!member && online) {
if (ownUser) {
member = &(room->resource());
nick = instance;
}
else
member = static_cast<MucRoomMember*>(room->appendResource(instance));
}
if (!member)
return false;
// Update contact UI. Set some chat history messages
if (userKicked || userBanned) {
String tmp(member->m_name + " was ");
const char* by = 0;
const char* r = 0;
if (userKicked) {
tmp << "kicked";
by = msg.getValue(YSTRING("muc.userkicked.by"));
r = msg.getValue(YSTRING("muc.userkicked.reason"));
}
else {
tmp << "banned";
by = msg.getValue(YSTRING("muc.userbanned.by"));
r = msg.getValue(YSTRING("muc.userbanned.reason"));
}
if (!TelEngine::null(by))
tmp << " by " << by;
if (!TelEngine::null(r))
tmp << " (" << r << ")";
addChatNotify(*room,tmp,msg.msgTime().sec());
}
bool changed = false;
// Update role and affiliation
const String& roleStr = msg[YSTRING("muc.role")];
int role = lookup(roleStr,MucRoomMember::s_roleName);
if (role != MucRoomMember::RoleUnknown && role != member->m_role) {
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s room=%s nick=%s role set to '%s'",
name().c_str(),acc->toString().c_str(),room->uri().c_str(),
member->m_name.c_str(),roleStr.c_str());
member->m_role = role;
changed = true;
if (role != MucRoomMember::RoleNone) {
// Notify role change
String text;
if (room->ownMember(member))
text << "You are now a ";
else
text << member->m_name + " is now a ";
addChatNotify(*room,text + roleStr + " in the room",msg.msgTime().sec());
}
}
int aff = msg.getIntValue("muc.affiliation",MucRoomMember::s_affName);
if (aff != MucRoomMember::AffUnknown && aff != member->m_affiliation) {
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s room=%s nick=%s affiliation set to '%s'",
name().c_str(),acc->toString().c_str(),room->uri().c_str(),
member->m_name.c_str(),msg.getValue("muc.affiliation"));
member->m_affiliation = aff;
if (member->m_affiliation == MucRoomMember::Outcast) {
String text;
if (room->ownMember(member))
text << "You are";
else
text << member->m_name + " is";
text << " no longer a room member";
addChatNotify(*room,text,msg.msgTime().sec());
}
changed = true;
}
// Update status
if (online != member->online()) {
// Create the room by setting a default config if this a new one
if (online && room->ownMember(member) && newRoom &&
room->resource().m_status == ClientResource::Connecting &&
member->m_affiliation == MucRoomMember::Owner)
Engine::enqueue(room->buildMucRoom("setconfig"));
if (member->m_status < ClientResource::Online)
member->m_status = ClientResource::Online;
else
member->m_status = ClientResource::Offline;
if (!room->ownMember(member)) {
String text(member->m_name);
text << " is " << lookup(member->m_status,ClientResource::s_statusName);
addChatNotify(*room,text,msg.msgTime().sec());
}
changed = true;
if (member->m_status == ClientResource::Online && room->ownMember(member)) {
// Send invitations
unsigned int count = room->m_params.getIntValue(YSTRING("internal.invite.count"));
if (count) {
const String& text = room->m_params[YSTRING("internal.invite.text")];
NamedIterator iter(room->m_params);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (ns->name() == YSTRING("internal.invite.contact"))
Engine::enqueue(buildMucRoom("invite",acc->toString(),
room->uri(),text,*ns));
}
}
room->m_params.clearParam(YSTRING("internal.invite"),'.');
}
// Re-connect?
if (room->ownMember(member) && !online &&
room->m_params.getBoolValue(YSTRING("internal.reconnect"))) {
room->m_params.clearParam(YSTRING("internal.reconnect"));
joinRoom(room);
}
}
// Update contact/instance
if (!room->ownMember(member)) {
if (mucContact)
changed = setChangedString(member->m_uri,mucContact) || changed;
if (mucInst)
changed = setChangedString(member->m_instance,mucInst) || changed;
}
// Handle nick changes
if (nick && nick != member->m_name) {
String text;
if (room->ownMember(member))
text << "You are";
else {
text << member->m_name << " is";
// Close old member's chat log
logCloseSession(room,false,member->m_name);
}
text << " now known as " << nick;
addChatNotify(*room,text,msg.msgTime().sec());
member->m_name = nick;
changed = true;
}
// Update
if (changed) {
updateMucRoomMember(*room,*member,&msg);
if (acc->resource().online() && room->ownMember(member) &&
(room->local() || room->remote()))
updateChatRoomsContactList(true,0,room);
}
return true;
}
// Show/hide the notification area (messages)
bool DefaultLogic::showNotificationArea(bool show, Window* wnd, NamedList* upd,
const char* notif)
{
if (!Client::self())
return false;
if (upd) {
Client::self()->updateTableRows(YSTRING("messages"),upd,false,wnd);
addTrayIcon(notif);
}
else if (!show)
removeTrayIcon(notif);
NamedList p("");
const char* ok = String::boolText(show);
p.addParam("check:messages_show",ok);
p.addParam("show:frame_messages",ok);
Client::self()->setParams(&p,wnd);
if (wnd)
Client::self()->setUrgent(wnd->id(),true,wnd);
return true;
}
// Show a roster change or failure notification
void DefaultLogic::showUserRosterNotification(ClientAccount* a, const String& oper,
Message& msg, const String& contactUri, bool newContact)
{
if (!a)
return;
NamedList list("");
NamedList* upd = 0;
String text;
const char* firstButton = 0;
bool update = (oper == YSTRING("update"));
const char* notif = "notification";
ClientContact* c = contactUri ? a->findContactByUri(contactUri) : 0;
String cName;
if (c)
buildContactName(cName,*c);
else
cName = contactUri;
if (update || oper == YSTRING("delete")) {
if (!c)
return;
notif = "info";
upd = buildNotifArea(list,"generic",a->toString(),contactUri,
"Friends list changed");
text << (update ? (newContact ? "Added" : "Updated") : "Removed");
text << " friend " << cName;
}
else if (oper == YSTRING("error")) {
if (!contactUri)
return;
ClientContact* c = a->findContactByUri(contactUri);
const String& req = msg["requested_operation"];
const char* what = 0;
if (req == "update") {
upd = buildNotifArea(list,"contactupdatefail",a->toString(),
contactUri,"Friend update failure");
what = (c ? "update" : "add");
}
else if (req == YSTRING("delete")) {
if (!c)
return;
upd = buildNotifArea(list,"contactremovefail",a->toString(),
contactUri,"Friend delete failure");
what = "remove";
}
else
return;
text << "Failed to " << what << " friend " << cName;
addError(text,msg);
}
else if (oper == YSTRING("queryerror")) {
upd = buildNotifArea(list,"rosterreqfail",a->toString(),String::empty(),
"Friends list failure");
firstButton = "Retry";
text << "Failed to retrieve the friends list";
addError(text,msg);
}
else {
if (oper == YSTRING("result"))
Debug(ClientDriver::self(),DebugAll,"Contact %s for '%s' account=%s confirmed",
msg.getValue("requested_operation"),msg.getValue("contact"),
a->toString().c_str());
return;
}
setGenericNotif(*upd,firstButton);
Debug(ClientDriver::self(),DebugAll,"Account '%s'. %s",
a->toString().c_str(),text.c_str());
text << "\r\nAccount: " << a->toString();
upd->addParam("text",text);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&list,notif);
}
// Handle actions from notification area. Return true if handled
bool DefaultLogic::handleNotificationAreaAction(const String& action, Window* wnd)
{
String id = action;
const TokenDict* act = s_notifPrefix;
for (; act && act->token; act++)
if (id.startSkip(act->token,false))
break;
if (!(act && act->token))
return false;
NamedList p("");
Client::self()->getTableRow(YSTRING("messages"),id,&p,wnd);
const String& type = p[YSTRING("item_type")];
const String& account = p[YSTRING("account")];
if (!(type && account))
return false;
bool handled = true;
bool remove = true;
if (type == YSTRING("subscription")) {
const String& contact = p[YSTRING("contact")];
if (!contact)
return false;
if (act->value == PrivNotificationOk) {
Engine::enqueue(Client::buildSubscribe(false,true,account,contact));
Engine::enqueue(Client::buildSubscribe(true,true,account,contact));
}
else if (act->value == PrivNotificationReject)
Engine::enqueue(Client::buildSubscribe(false,false,account,contact));
else
handled = false;
}
else if (type == YSTRING("loginfail")) {
if (act->value == PrivNotificationLogin) {
ClientAccount* acc = m_accounts->findAccount(account);
remove = acc && ::loginAccount(this,acc->params(),true);
}
else if (act->value == PrivNotificationAccEdit)
remove = internalEditAccount(false,&account,0,wnd);
else if (act->value == PrivNotificationAccounts) {
Window* w = Client::self()->getWindow(s_wndAcountList);
if (w) {
Client::self()->setSelect(s_accountList,account,w);
remove = Client::self()->setVisible(s_wndAcountList,true,true);
}
}
else
handled = false;
}
else if (type == YSTRING("mucinvite")) {
const String& room = p[YSTRING("room")];
if (!room)
return false;
if (act->value == PrivNotificationOk) {
ClientAccount* acc = m_accounts->findAccount(account);
if (acc) {
NamedList params("");
params.addParam("room_account",acc->toString());
params.addParam("room_uri",room);
// Check if we already have a room, use its nickname
const char* nick = 0;
MucRoom* r = acc->findRoomByUri(room);
if (r)
nick = r->m_params.getValue(YSTRING("nick"));
else if (acc->contact())
nick = acc->contact()->uri().getUser();
params.addParam("room_nick",nick);
params.addParam("room_password",p[YSTRING("password")]);
params.addParam("check:room_history",String::boolText(true));
s_tempWizards.append(new JoinMucWizard(m_accounts,&params));
}
else
remove = false;
}
else if (act->value == PrivNotificationReject) {
Message* m = buildMucRoom("decline",account,String::empty());
m->copyParams(p,YSTRING("room,contact,contact_instance"));
// TODO: implement reason
Engine::enqueue(m);
}
else
handled = false;
}
else if (type == YSTRING("incomingfile")) {
const String& chan = p[YSTRING("targetid")];
if (chan) {
if (act->value == PrivNotificationOk) {
const String& file = p[YSTRING("file_name")];
if (file)
remove = !chooseFileTransfer(false,s_fileOpenRecvPrefix + id,wnd,file);
}
else {
ClientDriver::dropChan(chan,"rejected");
remove = true;
}
}
}
else if (type == YSTRING("rosterreqfail")) {
if (act->value == PrivNotification1)
remove = queryRoster(m_accounts->findAccount(account));
}
else
return false;
if (handled) {
if (remove)
Client::self()->delTableRow(YSTRING("messages"),id,wnd);
}
else
Debug(ClientDriver::self(),DebugStub,"Unhandled notification area action='%s' type=%s",
act->token,type.c_str());
return handled;
}
// Save a contact
bool DefaultLogic::storeContact(ClientContact* c)
{
ClientAccount* a = c ? c->account() : 0;
if (!a)
return false;
MucRoom* room = c->mucRoom();
if (!room)
return false;
if (room->local()) {
String error;
if (!(a->setupDataDir(&error) && saveContact(a->m_cfg,room))) {
String text;
text << "Failed to save chat room " << room->uri();
text.append(error,"\r\n");
notifyGenericError(text,a->toString(),room->uri());
}
}
else
ClientLogic::clearContact(a->m_cfg,room);
Engine::enqueue(a->userData(true,"chatrooms"));
return true;
}
// Handle ok from account password/credentials input window
bool DefaultLogic::handleAccCredInput(Window* wnd, const String& name, bool inputPwd)
{
ClientAccount* acc = name ? m_accounts->findAccount(name) : 0;
if (!acc)
return false;
String prefix = inputPwd ? "inputpwd_" : "inputacccred_";
String pwd;
Client::self()->getText(prefix + "password",pwd,false,wnd);
if (!pwd)
return showError(wnd,"Account password is mandatory");
// Check username changes
if (!inputPwd) {
String user;
Client::self()->getText(prefix + "username",user,false,wnd);
if (!user)
return showError(wnd,"Account username is mandatory");
if (user != acc->params()[YSTRING("username")]) {
String newId;
buildAccountId(newId,acc->protocol(),user,
acc->params().getValue("domain",acc->params().getValue("server")));
if (m_accounts->findAccount(newId))
return showAccDupError(wnd);
NamedList account(acc->params());
account.assign(newId);
account.setParam("username",user);
account.setParam("password",pwd);
saveCheckParam(account,prefix,YSTRING("savepassword"),wnd);
return updateAccount(account,true,name);
}
}
acc->m_params.setParam("password",pwd);
saveCheckParam(acc->m_params,prefix,YSTRING("savepassword"),wnd);
acc->save(true,acc->params().getBoolValue(YSTRING("savepassword")));
if (acc->startup()) {
setAccountStatus(m_accounts,acc,0,0,false);
return true;
}
return ::loginAccount(this,acc->params(),true,false);
}
// Handle channel show/hide transfer/conference toggles
bool DefaultLogic::handleChanShowExtra(Window* wnd, bool show, const String& chan,
bool conf)
{
if (!(Client::valid() && chan))
return false;
NamedList p("");
if (channelItemAdjustUiList(p,show ? 1 : 0,true,chan,conf))
channelItemBuildUpdate(true,p,chan,conf,true);
Client::self()->setTableRow(s_channelList,chan,&p,wnd);
return true;
}
// Handle conf/transfer start actions in channel item
bool DefaultLogic::handleChanItemConfTransfer(bool conf, const String& name, Window* wnd)
{
if (!Client::valid())
return false;
String chan = name.substr(0,name.find(":"));
const char* suffix = conf ? "_conf_target" : "trans_target";
NamedString* target = s_generic.getParam(chan + suffix);
if (TelEngine::null(target))
return true;
NamedList params("");
params.addParam("target",*target);
params.addParam("channel_slave_type",conf ? "conference" : "transfer");
params.addParam("channel_master",chan);
// Add master data to slave (account, protocol ...) if not a full target
static const Regexp r("^[a-z0-9]\\+/");
if (!r.matches(target->safe())) {
ClientChannel* ch = ClientDriver::findChan(chan);
if (ch) {
params.copyParams(ch->clientParams(),"account,line,protocol");
TelEngine::destruct(ch);
}
}
if (callStart(params,wnd,s_actionCall)) {
s_generic.clearParam(target);
channelItemResetTarget(wnd,chan,conf);
if (conf)
ClientDriver::setConference(chan,true,0,true);
}
return true;
}
// Handle file share(d) related action
bool DefaultLogic::handleFileShareAction(Window* wnd, const String& name, NamedList* params)
{
if (!Client::valid())
return false;
ClientContact* c = 0;
// Show dirs/files we share
if (name == s_fileShare) {
c = getContactFromParamContext(m_accounts,params,s_chatContactList,wnd);
return showContactShareWnd(c);
}
if (name.startsWith("share_file:",false))
return showContactShareWnd(m_accounts->findContact(name.substr(11)));
// Show dirs/files shared by contact
if (name == s_fileShared) {
c = getContactFromParamContext(m_accounts,params,s_chatContactList,wnd);
return showContactSharedWnd(c);
}
if (name.startsWith("shared_file:",false))
return showContactSharedWnd(m_accounts->findContact(name.substr(12)));
// Item pressed in dir content, request/display directory
if (name == s_fileSharedDirsContent) {
String sel;
if (wnd)
Client::self()->getSelect(name,sel,wnd);
if (!sel)
return false;
String upDir;
if (Client::removeLastNameInPath(upDir,sel,'/',s_dirUp)) {
// Extract last dir up and last item
Client::removeLastNameInPath(upDir,upDir,'/');
if (upDir) {
// Select the directory, this will trigger content update
Client::self()->setSelect(s_fileSharedDirsList,upDir,wnd);
return true;
}
return false;
}
c = m_accounts->findContact(wnd->context());
if (!c)
return false;
String res;
String path;
sharedSplitId(sel,res,path);
ClientDir* d = c->getShared(res);
if (!d)
return true;
ClientFileItem* it = d->findChild(path);
if (!it)
return true;
if (it->directory())
// Select the directory, this will trigger content update
Client::self()->setSelect(s_fileSharedDirsList,sel,wnd);
return true;
}
else if (wnd) {
// Specific window commands
if (name == s_fileShareNew)
return chooseDirShareDir(m_accounts,wnd);
if (name == s_fileShareDel)
return handleShareDel(m_accounts,wnd->context(),wnd);
if (name == s_fileShareRename)
return beginEditSelected(s_fileShareList,wnd,"name");
if (name.startsWith(s_fileShareChooseDirPrefix,false)) {
String cid = name.substr(s_fileShareChooseDirPrefix.length());
return handleShareSet(true,m_accounts,cid,wnd,params);
}
return false;
}
return false;
}
// Handle file share(d) related select
bool DefaultLogic::handleFileShareSelect(Window* wnd, const String& name, const String& item,
const String& text, const NamedList* items)
{
if (name == s_fileSharedDirsList) {
// Multiple select not handled
if (items)
return false;
ClientContact* c = wnd ? m_accounts->findContact(wnd->context()) : 0;
if (!c)
return false;
Client::self()->clearTable(s_fileSharedDirsContent,wnd);
if (!item)
return true;
String resName;
String path;
sharedSplitId(item,resName,path);
ClientDir* res = c->getShared(resName);
if (res) {
ClientFileItem* ch = res->findChild(path);
ClientDir* d = ch ? ch->directory() : 0;
if (d) {
sharedContentUpdate(c,res,path,d,wnd);
return true;
}
}
return false;
}
if (name == s_fileSharedDirsContent) {
// Nothing to be done
return true;
}
if (name == s_fileShareList) {
if (!wnd)
return false;
bool canDel = false;
bool canEdit = false;
if (items) {
canDel = (0 != items->getParam(0));
canEdit = canDel && (0 == items->getParam(1));
}
else {
canDel = !item.null();
canEdit = canDel;
}
NamedList p("");
p.addParam("active:" + s_fileShareDel,String::boolText(canDel));
p.addParam("active:" + s_fileShareRename,String::boolText(canEdit));
Client::self()->setParams(&p,wnd);
return true;
}
return false;
}
// Handle file share(d) item changes from UI
bool DefaultLogic::handleFileShareItemChanged(Window* wnd, const String& name, const String& item,
const NamedList& params)
{
if (!Client::valid())
return false;
if (name == s_fileShareList) {
ClientContact* c = wnd ? m_accounts->findContact(wnd->context()) : 0;
if (!c)
return true;
NamedString* ns = c->share().getParam(item);
if (!ns)
return true;
if (!*ns)
Client::getLastNameInPath(*ns,ns->name());
const String& text = params[YSTRING("text.name")];
if (text != *ns) {
if (text && !Client::findParamByValue(c->share(),text,ns)) {
String old = *ns;
*ns = text;
c->saveShare();
if (changeContactShareInfo(c,old,*ns))
notifyContactShareInfoChanged(c);
}
else {
// Don't change: empty or another share with same name already exists
NamedList p("");
p.addParam("name",*ns);
Client::self()->setTableRow(name,item,&p,wnd);
}
}
return true;
}
return false;
}
// Handle file share(d) drop events
bool DefaultLogic::handleFileShareDrop(bool askOnly, Window* wnd, const String& ctrl,
NamedList& params, bool& retVal)
{
if (!Client::valid())
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) handleFileShareDrop() askOnly=%u wnd=(%p,%s) name=%s",
name().c_str(),askOnly,wnd,wnd ? wnd->toString().c_str():"",ctrl.c_str());
// Drop on local file system
if (ctrl == s_fileLocalFs) {
retVal = false;
if (!wnd)
return true;
if (askOnly) {
retVal = true;
return true;
}
const String& item = params[YSTRING("item")];
const String& itType = item ? params[YSTRING("item_type")] : String::empty();
String dir;
if (item) {
retVal = (item != s_dirUp) &&
(itType == YSTRING("dir") || itType == YSTRING("drive"));
if (retVal)
dir = item;
}
if (!retVal) {
Client::self()->getProperty(ctrl,"_yate_filesystem_path",dir,wnd);
retVal = !dir.null();
}
if (!retVal)
return true;
NamedIterator iter(params);
for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
if (!ns->name().startsWith("drop:"))
continue;
NamedList* nl = YOBJECT(NamedList,ns);
if (!nl)
continue;
String oper = ns->name().substr(5);
const String* what = *nl ? static_cast<String*>(nl) : (String*)(ns);
if (oper == YSTRING("yatedownload"))
m_ftManager->addShareDownload((*nl)[YSTRING("account")],
(*nl)[YSTRING("contact")],(*nl)[YSTRING("instance")],*what,dir,
wnd->id(),s_fileLocalFs);
}
return true;
}
// Share drop
if (ctrl == s_fileShareList) {
retVal = (wnd != 0);
if (retVal) {
if (!askOnly)
retVal = handleShareSet(true,m_accounts,wnd->context(),wnd,&params,false);
}
return true;
}
return false;
}
// Handle list item change action
bool DefaultLogic::handleListItemChanged(Window* wnd, const String& list, const String& item,
const NamedList& params)
{
// Specific handlers
if (handleFileShareItemChanged(wnd,list,item,params))
return false;
if (!Client::valid())
return false;
NamedList tmp("");
if (!Client::self()->getTableRow(list,item,&tmp,wnd))
return false;
String* enabled = tmp.getParam(YSTRING("check:enabled"));
if (enabled) {
bool ok = enabled->toBoolean();
if (list == s_accountList) {
ClientAccount* acc = m_accounts->findAccount(item);
if (acc && ok != acc->startup()) {
acc->startup(ok);
acc->save(true,acc->params().getBoolValue(YSTRING("savepassword")));
// Update telephony account selector(s)
updateTelAccList(ok,acc);
setAdvancedMode();
if (Client::s_engineStarted) {
if (ok)
setAccountStatus(m_accounts,acc);
else
loginAccount(acc->params(),false);
}
}
}
else if (list == s_logList) {
bool activeDel = ok || hasEnabledCheckedItems(list,wnd);
Client::self()->setActive(YSTRING("log_del"),activeDel,wnd);
}
else if (list == s_contactList) {
if (isLocalContact(&item,m_accounts)) {
bool activeDel = ok || hasEnabledCheckedItems(list,wnd);
Client::self()->setActive(YSTRING("abk_del"),activeDel,wnd);
}
else {
NamedList tmp("");
tmp.addParam("check:enabled",String::boolText(false));
Client::self()->setTableRow(list,item,&tmp,wnd);
}
}
}
return false;
}
// Handle drop events
bool DefaultLogic::handleDrop(bool askOnly, Window* wnd, const String& ctrl,
NamedList& params)
{
XDebug(ClientDriver::self(),DebugAll,"Logic(%s) handleDrop() wnd=(%p,%s) name=%s",
name().c_str(),wnd,wnd ? wnd->toString().c_str():"",ctrl.c_str());
bool retVal = false;
if (handleFileShareDrop(askOnly,wnd,ctrl,params,retVal))
return retVal;
return false;
}
// Handle file share info changed notification
void DefaultLogic::handleFileSharedChanged(ClientAccount* a, const String& contact,
const String& inst)
{
if (!(a && contact && inst))
return;
// Already requesting ?
String s;
PendingRequest::buildId(s,PendingRequest::SharedQuery,a->toString(),contact,inst);
if (PendingRequest::hasRequest(s))
return;
ClientContact* c = a->findContactByUri(contact);
if (!c)
return;
ClientDir* dir = 0;
c->removeShared(inst,&dir);
if (dir) {
removeSharedFromUI(c,dir);
TelEngine::destruct(dir);
}
ClientResource* res = c->findResource(inst);
if (res && res->caps().flag(ClientResource::CapFileInfo))
SharedPendingRequest::start(c,res);
enableChatActions(c,true,true,true);
showChatContactActions(*c);
}
/**
* DurationUpdate
*/
// Destructor
DurationUpdate::~DurationUpdate()
{
setLogic();
}
// Get a string representation of this object
const String& DurationUpdate::toString() const
{
return m_id;
}
// Build a duration string representation and add the parameter to a list
unsigned int DurationUpdate::buildTimeParam(NamedList& dest, unsigned int secNow,
bool force)
{
return buildTimeParam(dest,m_name,m_startTime,secNow,force);
}
// Build a duration string representation hh:mm:ss. The hours are added only if non 0
unsigned int DurationUpdate::buildTimeString(String& dest, unsigned int secNow,
bool force)
{
return buildTimeString(dest,m_startTime,secNow,force);
}
// Set the logic used to update this duration object. Remove from the old one
void DurationUpdate::setLogic(ClientLogic* logic, bool owner)
{
if (m_logic) {
m_logic->removeDurationUpdate(this,false);
m_logic = 0;
}
m_logic = logic;
if (m_logic)
m_logic->addDurationUpdate(this,owner);
}
// Update UI if duration is non 0
unsigned int DurationUpdate::update(unsigned int secNow, const String* table,
Window* wnd, Window* skip, bool force)
{
NamedList p("");
unsigned int duration = buildTimeParam(p,secNow,force);
if ((duration || force) && Client::self()) {
if (table)
Client::self()->setTableRow(*table,toString(),&p,wnd,skip);
else
Client::self()->setParams(&p,wnd,skip);
}
return duration;
}
// Build a duration string representation and add the parameter to a list
unsigned int DurationUpdate::buildTimeParam(NamedList& dest, const char* param,
unsigned int secStart, unsigned int secNow, bool force)
{
String tmp;
unsigned int duration = buildTimeString(tmp,secStart,secNow,force);
if (duration || force)
dest.addParam(param,tmp);
return duration;
}
// Build a duration string representation hh:mm:ss. The hours are added only if non 0
unsigned int DurationUpdate::buildTimeString(String& dest, unsigned int secStart,
unsigned int secNow, bool force)
{
if (secNow < secStart)
secNow = secStart;
unsigned int duration = secNow - secStart;
if (!(duration || force))
return 0;
unsigned int hrs = duration / 3600;
if (hrs)
dest << hrs << ":";
unsigned int rest = duration % 3600;
unsigned int mins = rest / 60;
unsigned int secs = rest % 60;
dest << ((hrs && mins < 10) ? "0" : "") << mins << ":" << (secs < 10 ? "0" : "") << secs;
return duration;
}
// Release memory. Remove from updater
void DurationUpdate::destroyed()
{
setLogic();
RefObject::destroyed();
}
/* vi: set ts=8 sw=4 sts=4 noet: */