/** @file GTalkBotBundle.cpp * @author Alessandro Polo * @version $Id: GTalkBotBundle.cpp 734 2009-10-14 04:13:08Z alex $ * @brief * File containing methods for the wosh::services::GTalkBotBundle class. * The header for this class can be found in GTalkBotBundle.h, check that file * for class description. ****************************************************************************/ /* Copyright (c) 2007-2009, WOSH - Wide Open Smart Home * by Alessandro Polo - OpenSmartHome.com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSmartHome.com WOSH nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ #include "GTalkBotBundle.h" #include "GTalkBotImpl.h" #include #include #include #include #include #include using namespace std; namespace wosh { namespace services { ////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS GTalkBotBundle::GTalkBotBundle() : BundleGeneric() { BundleGeneric::setName( _GTalkBot_NAME ); BundleGeneric::setType( "wosh::services::GTalkBot" ); Properties.setProperty( _Bundle_KEY_Version, _GTalkBot_VERSION ); Interfaces.add( wosh::interfaces::Service::getInterfaceName() ); Interfaces.add( _GTalkBot_TYPE ); //Log.setLevel( Logger::VERBOSE ); Log.log( Logger::VERBOSE, " Configuring GTalkBotImpl worker.." ); this->gtalkWorker = new GTalkBotImpl(); this->gtalkWorker->setServer( "", 0 ); this->gtalkWorker->setAccount( "", "" ); this->gtalkWorker->setAutoPresence( false ); this->gtalkWorker->setLogger( &Log ); this->gtalkWorker->setThreadListener(this); this->gtalkWorker->setGTalkBotListener(this); Log.log( Logger::VERBOSE, " Setting default properties and permissions.." ); Properties.setProperty( _GTalkBot_KEY_ServerAddress, "", Permission( Permission::RW, Permission::RW, Permission::Read) ); Properties.setProperty( _GTalkBot_KEY_ServerPort, "", Permission( Permission::RW, Permission::RW, Permission::Read) ); Properties.setProperty( _GTalkBot_KEY_UserName, "", Permission( Permission::RW, Permission::RW, Permission::Read) ); Properties.setProperty( _GTalkBot_KEY_UserPassword, "", Permission( Permission::RW, Permission::RW, Permission::Read) ); Log.log( Logger::VERBOSE, " Registering methods.." ); MethodMessageResponse* mmSendMessageToUser = new MethodMessageResponse( _Communicator_METHOD_sendmessage, "send message to (wosh) username" ); mmSendMessageToUser->setMethod( this, (MethodMessageResponsePtr)>alkBotBundle::mmDoSendMessageToUser ); mmSendMessageToUser->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX ); Methods.registerMethod(mmSendMessageToUser); MethodRequest* mmListContacts = new MethodRequest( "list", "List contacts" ); mmListContacts->setMethod( this, (MethodRequestPtr)>alkBotBundle::mmDoListContacts ); mmListContacts->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX ); Methods.registerMethod(mmListContacts); setBundleState(Bundle::CREATED, false); } GTalkBotBundle::~GTalkBotBundle() { Log.log( Logger::VERBOSE, " Destroying.." ); if ( isBundleRunning() ) { Log.log( Logger::WARNING, "~GTalkBotBundle() : Destroying while RUNNING! Trying to stop.." ); bundleStop(); } delete this->gtalkWorker; this->gtalkWorker = NULL; Log.log( Logger::VERBOSE, ":~GTalkBotBundle() : Destroyed." ); } //////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL WRESULT GTalkBotBundle::bundleStart() { if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STARTING) ) return WRET_ERR_WRONG_STATE; WRESULT ret = BundleGeneric::start_SynchThread( this->gtalkWorker ); // BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event() return ret; } WRESULT GTalkBotBundle::bundleStop() { if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STOPPING) ) return WRET_ERR_WRONG_STATE; WRESULT ret = BundleGeneric::stop_SynchThread( this->gtalkWorker ); // BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event() return ret; } ////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL ////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES void GTalkBotBundle::busMessage( const Message& message, Bus* source ) { if ( message.isEmpty() ) return; BundleGeneric::busMessage(message, source); if ( !message.getContent()->isNotification() ) return; ///@todo eval notification notify( message.getContent()->asNotification() ); } //////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES ////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS void GTalkBotBundle::raiseEvent( Fact* fact ) { Message* msg_event = new Message(); msg_event->setSource( this ); msg_event->setContent( fact ); msg_event->setDestinationBroadcast(); BusCore.postMessage(msg_event); } //////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS bool GTalkBotBundle::readingProperty( Property* property_curr, const PropertiesProvider* source ) { (void)property_curr; (void)source; return true; } bool GTalkBotBundle::updatingProperty( Property* property_new, const Property* property_old, const PropertiesProvider* source ) { (void)source; (void)property_old; if ( property_new == NULL ) // property was removed return true; if ( property_new->getKey() == _GTalkBot_KEY_ServerAddress || property_new->getKey() == _GTalkBot_KEY_ServerPort || property_new->getKey() == _GTalkBot_KEY_UserName || property_new->getKey() == _GTalkBot_KEY_UserPassword ) { if ( this->gtalkWorker->isThreadRunning() ) return false; bool ret = false; if ( property_new->getKey() == _GTalkBot_KEY_ServerAddress ) ret = this->gtalkWorker->setServerAddress( property_new->getValue().getData() ); else if ( property_new->getKey() == _GTalkBot_KEY_ServerPort ) ret = this->gtalkWorker->setServerPort( property_new->getValue().toInteger() ); else if ( property_new->getKey() == _GTalkBot_KEY_UserName ) ret = this->gtalkWorker->setAccountUserName( property_new->getValue().getData() ); else if ( property_new->getKey() == _GTalkBot_KEY_UserPassword ) ret = this->gtalkWorker->setAccountUserPassword( property_new->getValue().getData() ); return ret; } return BundleGeneric::updatingProperty( property_new, property_old, source ); } ///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS ////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// THREAD EVENTS void GTalkBotBundle::thread_event( Thread* thread, Thread::THREAD_STATE event ) { long id = 0; if ( thread != NULL ) id = thread->getThreadID(); Log.log( Logger::VERBOSE, ":thread_event(%i) : %s", id, Thread::getThreadStateAsString(event).c_str() ); switch(event) { case Thread::RUNNING: { setBundleState( Bundle::STARTED ); break; } case Thread::STOPPED: { setBundleState( Bundle::STOPPED ); break; } default: break; } } /////////////////////////////////////////////////////////////////////////////////////////////// THREAD EVENTS ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// GTALKBOT EVENTS void GTalkBotBundle::gtalkbot_UserStatus( GTalkBotImpl* bot, const string& accountname, int status ) { (void)bot; if ( accountname == this->gtalkWorker->getAccountName() ) { Log.log( Logger::VERBOSE, ":gtalkbot_UserStatus(%s) : I'm online, hopefully i knew :)", accountname.c_str() ); return; } std::string username = getUsernameFromAccountName(accountname); if ( username == "" ) { Log.log( Logger::WARNING, ":gtalkbot_UserStatus() : Account %s not recognized", accountname.c_str() ); return; } ///@todo EVAL USER/SOURCE vs. SECURITY RULES here Fact* fact = NULL; if ( status == 0 ) { fact = new Fact( _Communicator_EVENT_UserOffline, username ); this->contacts.set(accountname, -Utilities::std_time()); //update contacts' timestamp Log.log( Logger::INFO, ":gtalkbot_UserStatus() : User %s is offline (as %s)", username.c_str(), accountname.c_str() ); } else if ( status == 1 ) { fact = new Fact( _Communicator_EVENT_UserOnline, username ); this->contacts.set(accountname, Utilities::std_time()); //update contacts' timestamp Log.log( Logger::INFO, ":gtalkbot_UserStatus() : User %s is online (as %s)", username.c_str(), accountname.c_str() ); } else { this->contacts.set(accountname, 0); Log.log( Logger::VERBOSE, ":gtalkbot_UserStatus() : Ignored unhandled status of %s [] (%s)", username.c_str(), accountname.c_str(), status ); return; } // notify user presence raiseEvent( fact ); } void GTalkBotBundle::gtalkbot_Message( GTalkBotImpl* bot, const string& accountname, const string& message ) { (void)bot; // recognize the user string username = getUsernameFromAccountName(accountname); if ( username == "" ) { Log.log( Logger::WARNING, ":gtalkbot_UserStatus() : Account %s not recognized", accountname.c_str() ); return; } Log.log( Logger::VERBOSE, ":gtalkbot_Message() : Recognized %s as %s", accountname.c_str(), username.c_str() ); ///@todo EVAL USER/SOURCE vs. SECURITY RULES here this->contacts.set(accountname, Utilities::std_time()); //update contacts' timestamp // inject TEXT_MESSAGE into WOSH TextMessage* txtmsg = new TextMessage(); txtmsg->setSender_ID( accountname ); txtmsg->setSender_User( username ); txtmsg->setRecipent_ID( this->gtalkWorker->getAccountName() ); txtmsg->setRecipent_User( "wosh" ); txtmsg->setMessage( message ); Fact* msgFact = new Fact( _Communicator_EVENT_Message, txtmsg ); raiseEvent( msgFact ); } ///////////////////////////////////////////////////////////////////////////////////////////// GTALKBOT EVENTS ////////////////////////////////////////////////////////////////////////////////////////////////////////////// std::string GTalkBotBundle::getUsernameFromAccountName( const string& accountname ) { std::vector users = UserManager::findUserByProperty( "IM_Gtalk_name", Data(accountname) ); if ( users.size() == 0 ) { Log.log( Logger::WARNING, ":getUsernameFromAccountName() : User '%s' not recognized!", accountname.c_str() ); return ""; } else if ( users.size() > 1 ) { Log.log( Logger::WARNING, ":getUsernameFromAccountName() : User '%s' not recognized!", accountname.c_str() ); return ""; } return users[0]; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// std::string GTalkBotBundle::getAccountNameFromUsername( const string& username ) { bool found = UserManager::isUser(username); if ( !found ) { return ""; } return UserManager::getUserProperty(username, "IM_Gtalk_name").getData(); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool GTalkBotBundle::isUserReachable( const string& username ) { std::string accountname = getAccountNameFromUsername(username); if ( accountname == "" ) return false; MutexLockerRead mL(&this->contacts.mutex()); if ( !this->contacts.existsKey_(accountname) ) return false; if ( this->contacts[accountname] < 0 ) // negative means he is offline from abs(.) return false; return true; } bool GTalkBotBundle::isUserRegistered( const string& username ) { std::string accountname = getAccountNameFromUsername(username); if ( accountname == "" ) return false; if ( !this->contacts.existsKey(accountname) ) return false; return true; } long GTalkBotBundle::getUserLastSeen( const string& username ) { std::string accountname = getAccountNameFromUsername(username); if ( accountname == "" ) return 0; MutexLockerRead mL(&this->contacts.mutex()); if ( this->contacts.existsKey_(accountname) ) return 0; if ( this->contacts[accountname] < 0 ) // negative means he is offline from abs(.) return -this->contacts[accountname]; return this->contacts[accountname]; } WRESULT GTalkBotBundle::sendMessageToUser( const string& wosh_username, const string& message ) { std::string accountname = getAccountNameFromUsername(wosh_username); return sendMessageTo( accountname, message ); } WRESULT GTalkBotBundle::sendMessageTo( const string& gtalk_username, const string& message ) { if ( gtalk_username == "" ) return WRET_ERR_PARAM; bool sent = this->gtalkWorker->sendMessage( gtalk_username, message ); if ( !sent ) { Log.log( Logger::CRITICAL, ":sendMessageTo(%s) failed sending message", gtalk_username.c_str() ); return WRET_ERR_INTERNAL; } return WRET_OK; } WRESULT GTalkBotBundle::notify( const Notification* notification ) { if ( notification == NULL ) return WRET_ERR_PARAM; WRESULT ret = WRET_OK; if ( notification->isForAnyone() || notification->getUserName().size() == 0 ) { // should iterate when group-select or/and ? ret = WRET_ERR_INTERNAL; } else return sendMessageToUser( notification->getUserName(), notification->getSummary() ); return ret; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS Response* GTalkBotBundle::mmDoListContacts( const Request* request ) { if ( request == NULL ) return NULL; // retrieve options string option = request->getArgument().getData(); Log.log( Logger::VERBOSE, "mmDoListContacts(%s)", option.c_str() ); std::vector result; if ( option == "s" || option == "short" ) { DataList* list = new DataList(); this->contacts.mutex().lockForRead(); std::map::const_iterator it; std::map::const_iterator it_end = this->contacts.stdEnd(); for ( it=this->contacts.stdBegin(); it!=it_end; it++ ) { list->add( new Data(it->first) ); } this->contacts.mutex().unLock(); return new Response( request->getMethod(), list ); } else { std::string temp; this->contacts.mutex().lockForRead(); Table* tbl = new Table( this->contacts.size_() + 1, 2 ); tbl->setTableName("Contacts"); tbl->setHeader(true); tbl->set( new Data("Name"), 0, 0); tbl->set( new Data("Status"), 0, 1); unsigned int iRow = 1; std::map::const_iterator it; std::map::const_iterator it_end = this->contacts.stdEnd(); for ( it=this->contacts.stdBegin(); it!=it_end; it++ ) { tbl->set( new Data(getUsernameFromAccountName(it->first)), iRow, 0); if ( it->second > 0 ) tbl->set( new Data("ONLINE at " + Utilities::getTimeStampUTF(it->second)), iRow, 1); else if ( it->second < 0 ) tbl->set( new Data("OFFLINE from " + Utilities::getTimeStampUTF(it->second)), iRow, 1); else tbl->set( new Data("UNKNOWN"), iRow, 1); ++iRow; } this->contacts.mutex().unLock(); return new Response( request->getMethod(), tbl ); } } Response* GTalkBotBundle::mmDoSendMessageToUser( const Message* requestMessage ) { if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL; if ( !requestMessage->getContent()->isRequest() ) return NULL; const Request* request = requestMessage->getContent()->asRequest(); std::string username = ""; std::string message = ""; if ( strcmp( request->getData()->getClassName(), "wosh::DataList") == 0 ) { const DataList* dl = dynamic_cast(request->getData()); if ( dl->size() < 2 ) { if ( request->doOmitResponse() ) return NULL; return new Response( request->getMethod(), WRET_ERR_PARAM, "Invalid arguments [username, message]" ); } username = dl->get(0).getData(); dl->join(message, " ", 1); } std::string sender = ""; if ( requestMessage->getSecurityPayload() != NULL ) sender = requestMessage->getSecurityPayload()->getUser(); Log.log( Logger::VERBOSE, "mmDoSendMessageToUser(%s) : %s", username.c_str(), message.c_str() ); if ( sender != username && sender != "wosh ") message += " [by " + sender + "]"; WRESULT ret = sendMessageToUser( username, message ); if ( request->doOmitResponse() ) return NULL; if ( WSUCCEEDED(ret) ) return new Response( request->getMethod(), ret, "Sent!" ); return new Response( request->getMethod(), ret, "Internal Error!" ); } ///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS ////////////////////////////////////////////////////////////////////////////////////////////////////////////// const std::string& GTalkBotBundle::getServerAddress() const { return this->gtalkWorker->getServerAddress(); } int GTalkBotBundle::getServerPort() const { return this->gtalkWorker->getServerPort(); } const std::string& GTalkBotBundle::getAccountName() const { return this->gtalkWorker->getAccountName(); } bool GTalkBotBundle::getAutoPresence() const { return this->gtalkWorker->getAutoPresence(); } int GTalkBotBundle::getPriority() const { return this->gtalkWorker->getPriority(); } bool GTalkBotBundle::setServer( const std::string& address, int port ) { return this->gtalkWorker->setServer(address, port); } bool GTalkBotBundle::setAccount( const std::string& username, const std::string& password ) { return this->gtalkWorker->setAccount(username, password); } bool GTalkBotBundle::setAutoPresence( bool value ) { return this->gtalkWorker->setAutoPresence(value); } bool GTalkBotBundle::setPriority( int value ) { return this->gtalkWorker->setPriority(value); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// }; // namespace services }; // namespace wosh