12527 lines
389 KiB
C++
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(),¶ms))
|
|
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,¶ms,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,¶ms,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,¶ms,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(¶ms);
|
|
}
|
|
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(¶ms,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,¶ms));
|
|
}
|
|
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,¶ms,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: */
|