rk_remokon_qt.cpp

Go to the documentation of this file.
00001 /*
00002  !concept {:name => "QXmpp and Lua based remote control mechanism"}
00003 */
00004 
00005 #include "rk_remokon.h"
00006 #include "rk_remokon_qt.hpp"
00007 
00008 #include "ac_app_context.h"
00009 #include "cf_query.h" // get_ConfigDb_int
00010 #include "er_errors.h"
00011 
00012 #include "QXmppLogger.h"
00013 
00014 #include <stdlib.h>
00015 
00016 // --------------------------------------------------
00017 // utilities
00018 // --------------------------------------------------
00019 
00020 #define TRAPFATAL(_stm) {         \
00021     try {             \
00022       _stm ;              \
00023     } catch (const std::exception &ex) {      \
00024       er_log_none(er_FATAL, "remokon: %s", ex.what());    \
00025     }               \
00026   }
00027 
00028 #define GTRAP(_ret, _stm) {         \
00029     try {             \
00030       _stm ;              \
00031     } catch (const std::exception &ex) {      \
00032       if (error)            \
00033   *error = gx_error_new(domain_qt, -1,      \
00034             "remokon: %s", ex.what());  \
00035       return _ret;            \
00036     }               \
00037   }
00038 
00039 // --------------------------------------------------
00040 // _rk_Remokon
00041 // --------------------------------------------------
00042 
00043 static lua_State* new_my_lua()
00044 {
00045   lua_State* L = cl_lua_new_libs();
00046   if (!L) {
00047     throw std::bad_alloc();
00048   }
00049   return L;
00050 }
00051 
00052 _rk_Remokon::_rk_Remokon() :
00053   iIsActive(false), L(new_my_lua())
00054 {
00055   this->params.server = ac_STATIC_GET(remokon_host);
00056   this->params.port = ac_STATIC_GET(remokon_port);
00057   this->params.password = ac_STATIC_GET(remokon_password);
00058   this->params.jid = ac_STATIC_GET(jid);
00059 #if defined(__SYMBIAN32__)
00060   this->params.iap_id = get_config_iap_id();
00061 #endif /* __SYMBIAN32__ */
00062 
00063   this->iHaveConfig = ((this->params.server != NULL) &&
00064            (this->params.password != NULL) &&
00065            (this->params.jid != NULL));
00066 
00067   this->iAutostartEnabled = (force_get_ConfigDb_bool("remokon.autostart", FALSE));
00068 
00069   if (this->iHaveConfig) {
00070     logg("Jabber config: server %s:%d, jid '%s', auto %d",
00071    this->params.server, this->params.port,
00072    this->params.jid,
00073    this->iAutostartEnabled);
00074 
00075     iXmppConfiguration.setHost(params.server);
00076     iXmppConfiguration.setPort(params.port);
00077     iXmppConfiguration.setJid(params.jid);
00078     iXmppConfiguration.setPassword(params.password);
00079   }
00080 
00081   iXmppPresence.setType(QXmppPresence::Available);
00082   QXmppPresence::Status& stat = iXmppPresence.status();
00083   stat.setType(QXmppPresence::Status::Chat);
00084 #if defined(__SYMBIAN32__)
00085   stat.setStatusText(QString("Logging away on Symbian."));
00086 #else
00087   stat.setStatusText(QString("Logging away on a PC."));
00088 #endif /* __SYMBIAN32__ */
00089 
00090   // Desired security policy. We are strict on Symbian, since there we
00091   // assume the required certificate must have been globally
00092   // installed. For testing on the desktop we do not require that, nor
00093   // do we know if QXmpp allows for custom CA configuration.
00094 #if defined(__SYMBIAN32__)
00095   iXmppConfiguration.setIgnoreSslErrors(false);
00096   iXmppConfiguration.setUseSASLAuthentication(true);
00097   iXmppConfiguration.setStreamSecurityMode(QXmppConfiguration::TLSRequired);
00098   iXmppConfiguration.setSASLAuthMechanism(QXmppConfiguration::SASLDigestMD5);
00099 #endif /* __SYMBIAN32__ */
00100 
00101   // Note that "connect" produces a boolean return value if you want
00102   // to check.
00103   connect(&iSession, SIGNAL(error(QXmppClient::Error)),
00104     this, SLOT(gotJabberError(QXmppClient::Error)));
00105   connect(&iSession, SIGNAL(messageReceived(const QXmppMessage&)),
00106     this, SLOT(gotJabberMessage(const QXmppMessage&)));
00107 
00108   iRunTimer.setSingleShot(true);
00109   connect(&iRunTimer, SIGNAL(timeout()),
00110     this, SLOT(runTimeout()));
00111 }
00112 
00113 _rk_Remokon::~_rk_Remokon()
00114 {
00115   stop();
00116   if (L) 
00117     lua_close(L);
00118 }
00119 
00120 void _rk_Remokon::start()
00121 {
00122   logh();
00123   if (!iIsActive) {
00124     iSession.connectToServer(iXmppConfiguration, iXmppPresence);
00125     iIsActive = true;
00126   }
00127 }
00128 
00129 void _rk_Remokon::stop()
00130 {
00131   if (iIsActive) {
00132     iSession.disconnectFromServer();
00133     iIsActive = false;
00134   }
00135 }
00136 
00137 static int SecsToMsecs(int secs)
00138 {
00139   long long ms64 = (long long)(secs) * 1000LL;
00140   if (ms64 > 0x7fffffffLL) ms64 = 0x7fffffffLL;
00141   return (int)ms64;
00142 }
00143 
00144 void _rk_Remokon::resetRunTimer()
00145 {
00146   // It is okay to call "start" even when "isActive".
00147   iRunTimer.start(SecsToMsecs(iRunForSecs));
00148 }
00149 
00150 // If already running (in timed mode or otherwise), will be put into
00151 // timed mode, with the specified time period. This also resets any
00152 // existing countdown timer.
00153 void _rk_Remokon::startTimed(int secs)
00154 {
00155   logh();
00156   if (secs <= 0)
00157     return;
00158   iRunForSecs = secs;
00159   resetRunTimer();
00160   if (!iIsActive)
00161     start();
00162 }
00163 
00164 void _rk_Remokon::runTimeout()
00165 {
00166   log_db_log_status(ac_global_LogDb, NULL, 
00167         "remokon: stopping after %d secs", iRunForSecs);
00168   TRAPFATAL(stop());
00169 }
00170 
00171 void _rk_Remokon::send(const QString& toJid, const QString& msgText)
00172 {
00173   iSession.sendMessage(toJid, msgText);
00174 }
00175 
00176 // Not sure yet if we need to handle any of these errors. The client
00177 // object is supposed to itself do some retrying. For now we just log.
00178 void _rk_Remokon::gotJabberError(QXmppClient::Error anError)
00179 {
00180   switch (anError)
00181     {
00182     case QXmppClient::SocketError:
00183       {
00184         // Unfortunately we cannot use errorString() on the socket
00185         // since the API does not give us a handle to the socket.
00186   QAbstractSocket::SocketError error = iSession.socketError();
00187   er_log_none(0, "remokon: %d (%s) %d", 
00188         anError, "QXmppClient::SocketError", error);
00189         break;
00190       }
00191     case QXmppClient::KeepAliveError:
00192       {
00193   er_log_none(0, "remokon: %d (%s)", 
00194         anError, "QXmppClient::KeepAliveError");
00195         break;
00196       }
00197     case QXmppClient::XmppStreamError:
00198       {
00199   QXmppStanza::Error::Condition error = iSession.xmppStreamError();
00200   er_log_none(0, "remokon: %d (%s) %d", 
00201         anError, "QXmppClient::XmppStreamError", error);
00202         break;
00203       }
00204     default:
00205       {
00206   er_log_none(0, "remokon: %d (unknown QXmpp error)", anError);
00207         break;
00208       }
00209     }
00210 }
00211 
00212 class LuaStack
00213 {
00214 private:
00215   lua_State* L;
00216 public:
00217   int pop;
00218 public:
00219   LuaStack(lua_State* aL) : L(aL), pop(0) {}
00220   ~LuaStack() { 
00221     if (pop) {
00222       lua_pop(L, pop);
00223       //logg("remokon Lua stack: popped %d", pop);
00224     }
00225   }
00226 };
00227 
00228 #define TOCSTR(exp) ((exp).toUtf8().data())
00229 
00230 void _rk_Remokon::gotJabberMessage(const QXmppMessage& msg)
00231 {
00232   if (msg.body().isEmpty())
00233     return;
00234 
00235   resetRunTimer();
00236 
00237   QByteArray luaStrBa = msg.body().toUtf8();
00238   const char* luaStr = luaStrBa.data();
00239   logg("remote message from '%s': '%s'", 
00240        TOCSTR(msg.from()), 
00241        luaStr);
00242 
00243   // If set, points to a literal or something within Lua state.
00244   const gchar* replyText = NULL;
00245 
00246   LuaStack stack(L);
00247   int level = lua_gettop(L);
00248   int& pop = stack.pop;
00249 
00250   int res = (luaL_loadstring(L, luaStr) || lua_pcall(L, 0, LUA_MULTRET, 0));
00251   if (res != 0) {
00252     pop = 1; // expecting error message only
00253     assert(pop == (lua_gettop(L) - level));
00254     assert(lua_isstring(L, -1));
00255     replyText = lua_tostring(L, -1);
00256     if (!replyText) replyText = "Error: out of memory";
00257     goto reply;
00258   }
00259 
00260   // Now we may have any number of values, and not all of them
00261   // necessarily strings.
00262   // 
00263   // Might be nice to get any values printed to string output and
00264   // concatenated, upto certain max length. Lua does have a "print"
00265   // function, but seems to use printf directly, so probably would
00266   // require a bit of work to have it use some safe sprintf instead.
00267   // Some macro magic in print.c could work.
00268   pop = lua_gettop(L) - level;
00269   logg("nresults is %d", pop);
00270   if (pop > 0) {
00271     if (pop > 1) {
00272       replyText = "<multiple results>";
00273     } else {
00274       // Stored within Lua state at least until the corresponding value is popped.
00275       replyText = lua_tostring(L, -1);
00276       if (!replyText) replyText = "<unconvertible expression>";
00277     }
00278     goto reply;
00279   }
00280 
00281   // Evaluated to nothing.
00282   replyText = "OK";
00283 
00284  reply:
00285   assert(replyText); 
00286   send(msg.from(), QString(replyText));
00287 }
00288 
00289 // --------------------------------------------------
00290 // public API
00291 // --------------------------------------------------
00292 
00293 extern "C"
00294 rk_Remokon* rk_Remokon_new(GError** error)
00295 {
00296 #if !defined(__SYMBIAN32__) && !defined(NDEBUG)
00297   QXmppLogger::getLogger()->setLoggingType(QXmppLogger::StdoutLogging);
00298 #endif
00299 
00300   rk_Remokon* self = NULL;
00301   GTRAP(NULL, self = q_check_ptr(new rk_Remokon));
00302   return self;
00303 }
00304 
00305 // Supports only partially initialized objects.
00306 extern "C"
00307 void rk_Remokon_destroy(rk_Remokon* self)
00308 {
00309   delete self;
00310 }
00311 
00312 extern "C"
00313 gboolean rk_Remokon_is_autostart_enabled(rk_Remokon* self)
00314 {
00315   // Former value is constant, the latter may vary.
00316   return (self->iHaveConfig && self->iAutostartEnabled);
00317 }
00318 
00319 #define no_config_error \
00320   gx_error_new(domain_cl2app, code_no_configuration, \
00321          "some Jabber config missing")
00322 
00323 // Does nothing if already started.
00324 extern "C"
00325 gboolean rk_Remokon_start(rk_Remokon* self, GError** error)
00326 {
00327   if (!self->iHaveConfig) {
00328     // No point in trying to start without proper configuration.
00329     if (error)
00330       *error = no_config_error;
00331     return FALSE;
00332   }
00333 
00334   if (!rk_Remokon_is_started(self)) {
00335     GTRAP(FALSE, self->start());
00336   }
00337   return TRUE;
00338 }
00339 
00340 // Supports only partially initialized objects.
00341 // Does nothing if already stopped.
00342 extern "C"
00343 void rk_Remokon_stop(rk_Remokon* self)
00344 {
00345   TRAPFATAL(self->stop());
00346 }
00347 
00348 extern "C"
00349 gboolean rk_Remokon_start_timed(rk_Remokon* self,
00350         int secs,
00351         GError** error)
00352 {
00353   if (!self->iHaveConfig) {
00354     // No point in trying to start without proper configuration.
00355     if (error)
00356       *error = no_config_error;
00357     return FALSE;
00358   }
00359 
00360   GTRAP(FALSE, self->startTimed(secs));
00361   return TRUE;
00362 }
00363 
00364 extern "C"
00365 gboolean rk_Remokon_reconfigure(rk_Remokon* self,
00366         const gchar* key,
00367         const gchar* value,
00368         GError** error)
00369 {
00370   if (strcmp(key, "remokon.autostart")) {
00371     self->iAutostartEnabled = force_lua_eval_bool(value, FALSE);
00372   }
00373 
00374 #if defined(__SYMBIAN32__)
00375   else if (strcmp(key, "iap") == 0) {
00376     // It should be safe to just set this value. A change won't
00377     // take effect before a reconnect, but this is okay, a Remokon
00378     // stop followed by start will allow for that via the API.
00379     self->params.iap_id = force_lua_eval_int(value, -1);
00380   }
00381 #endif /* __SYMBIAN32__ */
00382 
00383   return TRUE;
00384 }
00385 
00386 extern "C"
00387 gboolean rk_Remokon_is_started(rk_Remokon* self)
00388 {
00389   return self && self->iIsActive;
00390 }
00391   
00392 extern "C"
00393 gboolean rk_Remokon_is_connected(rk_Remokon* self)
00394 {
00395   return self && self->iSession.isConnected();
00396 }
00397 
00398 extern "C"
00399 gboolean rk_Remokon_send(rk_Remokon* self,
00400        const char* toJid,
00401        const char* msgText,
00402        GError** error)
00403 {
00404   if (!rk_Remokon_is_connected(self)) {
00405     if (error)
00406       *error = gx_error_new(domain_cl2app, code_not_connected, 
00407           "no Jabber connection");
00408     return FALSE;
00409   }
00410 
00411   GTRAP(FALSE, self->send(QString(toJid), QString(msgText)));
00412 
00413   return TRUE;
00414 }
00415 
00416 /**
00417 
00418 Copyright 2009-2011 Helsinki Institute for Information Technology
00419 (HIIT) and the authors. All rights reserved.
00420 
00421 Authors: Tero Hasu <tero.hasu@hut.fi>
00422 
00423 Permission is hereby granted, free of charge, to any person
00424 obtaining a copy of this software and associated documentation files
00425 (the "Software"), to deal in the Software without restriction,
00426 including without limitation the rights to use, copy, modify, merge,
00427 publish, distribute, sublicense, and/or sell copies of the Software,
00428 and to permit persons to whom the Software is furnished to do so,
00429 subject to the following conditions:
00430 
00431 The above copyright notice and this permission notice shall be
00432 included in all copies or substantial portions of the Software.
00433 
00434 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00435 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00436 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00437 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
00438 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
00439 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
00440 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00441 SOFTWARE.
00442 
00443  **/

ContextLogger2—ContextLogger2 Logger Daemon Internals—Generated on Mon May 2 13:49:56 2011 by Doxygen 1.6.1