/** * 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(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(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& 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(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(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(o->get()) : 0; } // Find a batch download bool findDownloadBatch(RefPointer& d, const String& acc, const String& contact, const String& inst); // Find a batch download by notify id bool findDownloadBatchNotify(RefPointer& 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(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(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(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 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(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(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(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(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(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(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(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(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(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(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(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(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(o->get()); if (!a->hasChat()) continue; for (ObjList* oc = a->contacts().skipNull(); oc; oc = oc->skipNext()) { ClientContact* cc = static_cast(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(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(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(oa->get()); for (ObjList* oc = a->contacts().skipNull(); oc; oc = oc->skipNext()) { ClientContact* c = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 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(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& 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& 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 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(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 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 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 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 d = static_cast(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(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(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(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(o->get()); ListIterator iter(acc->mucs()); for (GenObject* gen = 0; 0 != (gen = iter.get());) { MucRoom* room = static_cast(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(o->get()); if (!a->hasChat()) continue; for (ObjList* oo = a->contacts().skipNull(); oo; oo = oo->skipNext()) { ClientContact* c = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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 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(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(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(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(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: */