gloox  1.0
adhoc.cpp
00001 /*
00002   Copyright (c) 2004-2009 by Jakob Schroeter <js@camaya.net>
00003   This file is part of the gloox library. http://camaya.net/gloox
00004 
00005   This software is distributed under a license. The full license
00006   agreement can be found in the file LICENSE in this distribution.
00007   This software may not be copied, modified, sold or distributed
00008   other than expressed in the named license agreement.
00009 
00010   This software is distributed without any warranty.
00011 */
00012 
00013 
00014 #include "adhoc.h"
00015 #include "adhochandler.h"
00016 #include "adhoccommandprovider.h"
00017 #include "disco.h"
00018 #include "error.h"
00019 #include "discohandler.h"
00020 #include "clientbase.h"
00021 #include "dataform.h"
00022 #include "util.h"
00023 
00024 namespace gloox
00025 {
00026 
00027   static const char* cmdActionStringValues[] =
00028   {
00029     "execute", "cancel", "prev", "next", "complete"
00030   };
00031 
00032   static inline const std::string actionString( Adhoc::Command::Action action )
00033   {
00034     return util::lookup2( action, cmdActionStringValues );
00035   }
00036 
00037   static const char* cmdStatusStringValues[] =
00038   {
00039     "executing", "completed", "canceled"
00040   };
00041 
00042   static inline const std::string statusString( Adhoc::Command::Status status )
00043   {
00044     return util::lookup( status, cmdStatusStringValues );
00045   }
00046 
00047   static const char* cmdNoteStringValues[] =
00048   {
00049     "info", "warn", "error"
00050   };
00051 
00052   static inline const std::string noteString( Adhoc::Command::Note::Severity sev )
00053   {
00054     return util::lookup( sev, cmdNoteStringValues );
00055   }
00056 
00057   // ---- Adhoc::Command::Note ----
00058   Adhoc::Command::Note::Note( const Tag* tag )
00059     : m_severity( InvalidSeverity )
00060   {
00061     if( !tag || tag->name() != "note" )
00062       return;
00063 
00064     m_severity = (Severity)util::deflookup( tag->findAttribute( "type" ), cmdNoteStringValues, Info );
00065     m_note = tag->cdata();
00066   }
00067 
00068   Tag* Adhoc::Command::Note::tag() const
00069   {
00070     if( m_note.empty() || m_severity == InvalidSeverity )
00071       return 0;
00072 
00073     Tag* n = new Tag( "note", m_note );
00074     n->addAttribute( TYPE, noteString( m_severity ) );
00075     return n;
00076   }
00077   // ---- ~Adhoc::Command::Note ----
00078 
00079   // ---- Adhoc::Command ----
00080   Adhoc::Command::Command( const std::string& node, Adhoc::Command::Action action,
00081                            DataForm* form )
00082     : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_form( form ), m_action( action ),
00083       m_status( InvalidStatus ), m_actions( 0 )
00084   {
00085   }
00086 
00087   Adhoc::Command::Command( const std::string& node, const std::string& sessionid, Status status,
00088                            DataForm* form )
00089   : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ),
00090     m_form( form ), m_action( InvalidAction ), m_status( status ), m_actions( 0 )
00091   {
00092   }
00093 
00094   Adhoc::Command::Command( const std::string& node, const std::string& sessionid,
00095                            Adhoc::Command::Action action,
00096                            DataForm* form )
00097     : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ),
00098       m_form( form ), m_action( action ), m_actions( 0 )
00099   {
00100   }
00101 
00102   Adhoc::Command::Command( const std::string& node, const std::string& sessionid, Status status,
00103                            Action executeAction, int allowedActions,
00104                            DataForm* form )
00105     : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ),
00106       m_form( form ), m_action( executeAction ), m_status( status ), m_actions( allowedActions )
00107   {
00108   }
00109 
00110   Adhoc::Command::Command( const Tag* tag )
00111     : StanzaExtension( ExtAdhocCommand ), m_form( 0 ), m_actions( 0 )
00112   {
00113     if( !tag || tag->name() != "command" || tag->xmlns() != XMLNS_ADHOC_COMMANDS )
00114       return;
00115 
00116     m_node = tag->findAttribute( "node" );
00117     m_sessionid = tag->findAttribute( "sessionid" );
00118     m_status = (Status)util::lookup( tag->findAttribute( "status" ), cmdStatusStringValues );
00119 
00120     Tag* a = tag->findChild( "actions" );
00121     if( a )
00122     {
00123       // Multi-stage response
00124       m_action = (Action)util::deflookup2( a->findAttribute( "action" ), cmdActionStringValues, Complete );
00125       if( a->hasChild( "prev" ) )
00126         m_actions |= Previous;
00127       if( a->hasChild( "next" ) )
00128         m_actions |= Next;
00129       if( a->hasChild( "complete" ) )
00130         m_actions |= Complete;
00131     }
00132     else
00133     {
00134       m_action = (Action)util::deflookup2( tag->findAttribute( "action" ), cmdActionStringValues, Execute );
00135     }
00136 
00137     const ConstTagList& l = tag->findTagList( "/command/note" );
00138     ConstTagList::const_iterator it = l.begin();
00139     for( ; it != l.end(); ++it )
00140       m_notes.push_back( new Note( (*it) ) );
00141 
00142     Tag* x = tag->findChild( "x", "xmlns", XMLNS_X_DATA );
00143     if( x )
00144       m_form = new DataForm( x );
00145   }
00146 
00147   Adhoc::Command::~Command()
00148   {
00149     util::clearList( m_notes );
00150     delete m_form;
00151   }
00152 
00153   const std::string& Adhoc::Command::filterString() const
00154   {
00155     static const std::string filter = "/iq/command[@xmlns='" + XMLNS_ADHOC_COMMANDS + "']";
00156     return filter;
00157   }
00158 
00159   Tag* Adhoc::Command::tag() const
00160   {
00161     if( m_node.empty() )
00162       return 0;
00163 
00164     Tag* c = new Tag( "command" );
00165     c->setXmlns( XMLNS_ADHOC_COMMANDS );
00166     c->addAttribute( "node", m_node );
00167     if( m_actions != 0 )
00168     {
00169       // Multi-stage command response
00170 
00171       if( m_status != InvalidStatus )
00172         c->addAttribute( "status", statusString( m_status ) );
00173       else
00174         c->addAttribute( "status", statusString( Executing ) );
00175 
00176       Tag* actions = new Tag( c, "actions" );
00177 
00178       if( m_action != InvalidAction )
00179         c->addAttribute( "execute", actionString( m_action ) );
00180       else
00181         c->addAttribute( "execute", actionString( Complete ) );
00182 
00183       if( ( m_actions & Previous ) == Previous )
00184         new Tag( actions, "prev" );
00185       if( ( m_actions & Next ) == Next )
00186         new Tag( actions, "next" );
00187       if( ( m_actions & Complete ) == Complete )
00188         new Tag( actions, "complete" );
00189     }
00190     else
00191     {
00192       // Single-stage command request/response or Multi-stage command request
00193 
00194       if( m_action != InvalidAction )
00195         c->addAttribute( "action", actionString( m_action ) );
00196       if( m_status != InvalidStatus )
00197         c->addAttribute( "status", statusString( m_status ) );
00198     }
00199 
00200     if ( !m_sessionid.empty() )
00201       c->addAttribute( "sessionid", m_sessionid );
00202 
00203     if( m_form && *m_form )
00204       c->addChild( m_form->tag() );
00205 
00206     NoteList::const_iterator it = m_notes.begin();
00207     for( ; it != m_notes.end(); ++it )
00208       c->addChild( (*it)->tag() );
00209 
00210     return c;
00211   }
00212   // ---- ~Adhoc::Command ----
00213 
00214   // ---- Adhoc ----
00215   Adhoc::Adhoc( ClientBase* parent )
00216     : m_parent( parent )
00217   {
00218     if( !m_parent || !m_parent->disco() )
00219       return;
00220 
00221     m_parent->disco()->addFeature( XMLNS_ADHOC_COMMANDS );
00222     m_parent->disco()->registerNodeHandler( this, XMLNS_ADHOC_COMMANDS );
00223     m_parent->disco()->registerNodeHandler( this, EmptyString );
00224     m_parent->registerIqHandler( this, ExtAdhocCommand );
00225     m_parent->registerStanzaExtension( new Adhoc::Command() );
00226   }
00227 
00228   Adhoc::~Adhoc()
00229   {
00230     if( !m_parent || !m_parent->disco() )
00231       return;
00232 
00233     m_parent->disco()->removeFeature( XMLNS_ADHOC_COMMANDS );
00234     m_parent->disco()->removeNodeHandler( this, XMLNS_ADHOC_COMMANDS );
00235     m_parent->disco()->removeNodeHandler( this, EmptyString );
00236     m_parent->removeIqHandler( this, ExtAdhocCommand );
00237     m_parent->removeIDHandler( this );
00238     m_parent->removeStanzaExtension( ExtAdhocCommand );
00239   }
00240 
00241   StringList Adhoc::handleDiscoNodeFeatures( const JID& /*from*/, const std::string& /*node*/ )
00242   {
00243     StringList features;
00244     features.push_back( XMLNS_ADHOC_COMMANDS );
00245     return features;
00246 //    return StringList( 1, XMLNS_ADHOC_COMMANDS );
00247   }
00248 
00249   Disco::ItemList Adhoc::handleDiscoNodeItems( const JID& from, const JID& /*to*/, const std::string& node )
00250   {
00251     Disco::ItemList l;
00252     if( node.empty() )
00253     {
00254       l.push_back( new Disco::Item( m_parent->jid(), XMLNS_ADHOC_COMMANDS, "Ad-Hoc Commands" ) );
00255     }
00256     else if( node == XMLNS_ADHOC_COMMANDS )
00257     {
00258       StringMap::const_iterator it = m_items.begin();
00259       for( ; it != m_items.end(); ++it )
00260       {
00261         AdhocCommandProviderMap::const_iterator itp = m_adhocCommandProviders.find( (*it).first );
00262         if( itp != m_adhocCommandProviders.end()
00263             && (*itp).second
00264             && (*itp).second->handleAdhocAccessRequest( from, (*it).first ) )
00265         {
00266           l.push_back( new Disco::Item( m_parent->jid(), (*it).first, (*it).second ) );
00267         }
00268       }
00269     }
00270     return l;
00271   }
00272 
00273   Disco::IdentityList Adhoc::handleDiscoNodeIdentities( const JID& /*from*/, const std::string& node )
00274   {
00275     Disco::IdentityList l;
00276     StringMap::const_iterator it = m_items.find( node );
00277     l.push_back( new Disco::Identity( "automation",
00278                                node == XMLNS_ADHOC_COMMANDS ? "command-list" : "command-node",
00279                                it == m_items.end() ? "Ad-Hoc Commands" : (*it).second ) );
00280     return l;
00281   }
00282 
00283   bool Adhoc::handleIq( const IQ& iq )
00284   {
00285     if( iq.subtype() != IQ::Set )
00286       return false;
00287 
00288     const Adhoc::Command* ac = iq.findExtension<Adhoc::Command>( ExtAdhocCommand );
00289     if( !ac || ac->node().empty())
00290       return false;
00291 
00292     AdhocCommandProviderMap::const_iterator it = m_adhocCommandProviders.find( ac->node() );
00293     if( it != m_adhocCommandProviders.end() )
00294     {
00295       const std::string& sess = ac->sessionID().empty() ? m_parent->getID() : ac->sessionID();
00296       m_activeSessions[sess] = iq.id();
00297       (*it).second->handleAdhocCommand( iq.from(), *ac, sess );
00298       return true;
00299     }
00300 
00301     return false;
00302   }
00303 
00304   void Adhoc::handleIqID( const IQ& iq, int context )
00305   {
00306     if( context != ExecuteAdhocCommand )
00307       return;
00308 
00309     AdhocTrackMap::iterator it = m_adhocTrackMap.find( iq.id() );
00310     if( it == m_adhocTrackMap.end() || (*it).second.context != context
00311         || (*it).second.remote != iq.from() )
00312       return;
00313 
00314     switch( iq.subtype() )
00315     {
00316       case IQ::Error:
00317         (*it).second.ah->handleAdhocError( iq.from(), iq.error() );
00318         break;
00319       case IQ::Result:
00320       {
00321         const Adhoc::Command* ac = iq.findExtension<Adhoc::Command>( ExtAdhocCommand );
00322         if( ac )
00323           (*it).second.ah->handleAdhocExecutionResult( iq.from(), *ac );
00324         break;
00325       }
00326       default:
00327         break;
00328     }
00329     m_adhocTrackMap.erase( it );
00330   }
00331 
00332   void Adhoc::registerAdhocCommandProvider( AdhocCommandProvider* acp, const std::string& command,
00333                                             const std::string& name )
00334   {
00335     if( !m_parent || !m_parent->disco() )
00336       return;
00337 
00338     m_parent->disco()->registerNodeHandler( this, command );
00339     m_adhocCommandProviders[command] = acp;
00340     m_items[command] = name;
00341   }
00342 
00343   void Adhoc::handleDiscoInfo( const JID& from, const Disco::Info& info, int context )
00344   {
00345     if( context != CheckAdhocSupport )
00346       return;
00347 
00348     AdhocTrackMap::iterator it = m_adhocTrackMap.begin();
00349     for( ; it != m_adhocTrackMap.end() && (*it).second.context != context
00350                                        && (*it).second.remote  != from; ++it )
00351       ;
00352     if( it == m_adhocTrackMap.end() )
00353       return;
00354 
00355     (*it).second.ah->handleAdhocSupport( from, info.hasFeature( XMLNS_ADHOC_COMMANDS ) );
00356     m_adhocTrackMap.erase( it );
00357   }
00358 
00359   void Adhoc::handleDiscoItems( const JID& from, const Disco::Items& items, int context )
00360   {
00361     if( context != FetchAdhocCommands )
00362       return;
00363 
00364     AdhocTrackMap::iterator it = m_adhocTrackMap.begin();
00365     for( ; it != m_adhocTrackMap.end(); ++it )
00366     {
00367       if( (*it).second.context == context && (*it).second.remote == from )
00368       {
00369         StringMap commands;
00370         const Disco::ItemList& l = items.items();
00371         Disco::ItemList::const_iterator it2 = l.begin();
00372         for( ; it2 != l.end(); ++it2 )
00373         {
00374           commands[(*it2)->node()] = (*it2)->name();
00375         }
00376         (*it).second.ah->handleAdhocCommands( from, commands );
00377 
00378         m_adhocTrackMap.erase( it );
00379         break;
00380       }
00381     }
00382   }
00383 
00384   void Adhoc::handleDiscoError( const JID& from, const Error* error, int context )
00385   {
00386     AdhocTrackMap::iterator it = m_adhocTrackMap.begin();
00387     for( ; it != m_adhocTrackMap.end(); ++it )
00388     {
00389       if( (*it).second.context == context && (*it).second.remote == from )
00390       {
00391         (*it).second.ah->handleAdhocError( from, error );
00392 
00393         m_adhocTrackMap.erase( it );
00394       }
00395     }
00396   }
00397 
00398   void Adhoc::checkSupport( const JID& remote, AdhocHandler* ah )
00399   {
00400     if( !remote || !ah || !m_parent || !m_parent->disco() )
00401       return;
00402 
00403     TrackStruct track;
00404     track.remote = remote;
00405     track.context = CheckAdhocSupport;
00406     track.ah = ah;
00407     const std::string& id = m_parent->getID();
00408     m_adhocTrackMap[id] = track;
00409     m_parent->disco()->getDiscoInfo( remote, EmptyString, this, CheckAdhocSupport, id );
00410   }
00411 
00412   void Adhoc::getCommands( const JID& remote, AdhocHandler* ah )
00413   {
00414     if( !remote || !ah || !m_parent || !m_parent->disco() )
00415       return;
00416 
00417     TrackStruct track;
00418     track.remote = remote;
00419     track.context = FetchAdhocCommands;
00420     track.ah = ah;
00421     const std::string& id = m_parent->getID();
00422     m_adhocTrackMap[id] = track;
00423     m_parent->disco()->getDiscoItems( remote, XMLNS_ADHOC_COMMANDS, this, FetchAdhocCommands, id );
00424   }
00425 
00426   void Adhoc::execute( const JID& remote, const Adhoc::Command* command, AdhocHandler* ah )
00427   {
00428     if( !remote || !command || !m_parent || !ah )
00429       return;
00430 
00431     const std::string& id = m_parent->getID();
00432     IQ iq( IQ::Set, remote, id );
00433     iq.addExtension( command );
00434 
00435     TrackStruct track;
00436     track.remote = remote;
00437     track.context = ExecuteAdhocCommand;
00438     track.session = command->sessionID();
00439     track.ah = ah;
00440     m_adhocTrackMap[id] = track;
00441 
00442     m_parent->send( iq, this, ExecuteAdhocCommand );
00443   }
00444 
00445   void Adhoc::respond( const JID& remote, const Adhoc::Command* command, const Error* error )
00446   {
00447     if( !remote || !command || !m_parent )
00448       return;
00449 
00450     StringMap::iterator it = m_activeSessions.find( command->sessionID() );
00451     if( it == m_activeSessions.end() )
00452       return;
00453 
00454     IQ re( error ? IQ::Error : IQ::Result, remote, (*it).second );
00455     re.addExtension( command );
00456     if( error )
00457       re.addExtension( error );
00458     m_parent->send( re );
00459     m_activeSessions.erase( it );
00460   }
00461 
00462   void Adhoc::removeAdhocCommandProvider( const std::string& command )
00463   {
00464     if( !m_parent || !m_parent->disco() )
00465       return;
00466 
00467     m_parent->disco()->removeNodeHandler( this, command );
00468     m_adhocCommandProviders.erase( command );
00469     m_items.erase( command );
00470   }
00471 
00472 }