diff --git a/lib/Formatter.cpp b/lib/Formatter.cpp index 2f1631ba4..ed4bd9f61 100644 --- a/lib/Formatter.cpp +++ b/lib/Formatter.cpp @@ -110,15 +110,6 @@ Formatter::getState () return _state; } -bool -Formatter::startWebServices () -{ - if (_opts.webservice && !_webservices->isStarted ()) - return _webservices->start (); - else - return false; -} - bool Formatter::start (const string &file, string *errmsg) { @@ -129,8 +120,8 @@ Formatter::start (const string &file, string *errmsg) if (_state != GINGA_STATE_STOPPED) return false; - if (_opts.webservice && !_webservices->isStarted ()) - this->startWebServices (); + if (_opts.webservices) + this->_webservices->start (); // Parse document. g_assert_null (_doc); @@ -182,6 +173,10 @@ Formatter::start (const string &file, string *errmsg) // Sets formatter state. _state = GINGA_STATE_PLAYING; + // start webservices + if (_opts.webservices) + _webservices->start (); + return true; } @@ -450,7 +445,7 @@ Formatter::Formatter (const GingaOptions *opts) : Ginga (opts) _opts.width = 800; _opts.height = 600; _opts.debug = false; - _opts.webservice = false; + _opts.webservices = false; _opts.opengl = false; _opts.experimental = false; }; @@ -492,6 +487,16 @@ Formatter::getDocument () return _doc; } +/** + * @brief Gets WebServices. + * @return WebServices or null (no current document). + */ +WebServices * +Formatter::getWebServices () +{ + return _webservices; +} + /** * @brief Gets EOS flag. * @return EOS flag. @@ -557,6 +562,24 @@ Formatter::setOptionDebug (Formatter *self, const string &name, bool value) TRACE ("%s:=%s", name.c_str (), strbool (value)); } +/** + * @brief Sets WebServices option option of the given Formatter. + * @param self Formatter. + * @param name Must be the string "webservies". + * @param value webservies flag value. + */ +void +Formatter::setOptionWebServices (Formatter *self, const string &name, + bool value) +{ + g_assert (name == "webservices"); + if (value) + self->_webservices->start (); + else + self->_webservices->stop (); + TRACE ("%s:=%s", name.c_str (), strbool (value)); +} + /** * @brief Sets the experimental option of the given Formatter. * @param self Formatter. diff --git a/lib/Formatter.h b/lib/Formatter.h index 63c9fb78a..636bd07ba 100644 --- a/lib/Formatter.h +++ b/lib/Formatter.h @@ -43,7 +43,6 @@ class Formatter : public Ginga bool start (const std::string &, std::string *); bool stop (); - bool startWebServices (); void resize (int, int); void redraw (cairo_t *); @@ -64,11 +63,13 @@ class Formatter : public Ginga ~Formatter (); Document *getDocument (); + WebServices *getWebServices (); bool getEOS (); void setEOS (bool); static void setOptionBackground (Formatter *, const string &, string); static void setOptionDebug (Formatter *, const string &, bool); + static void setOptionWebServices (Formatter *, const string &, bool); static void setOptionExperimental (Formatter *, const string &, bool); static void setOptionOpenGL (Formatter *, const string &, bool); static void setOptionSize (Formatter *, const string &, int); @@ -97,7 +98,7 @@ class Formatter : public Ginga /// @brief Current WebServices. WebServices *_webservices; - + /// @brief Current document tree. Document *_doc; diff --git a/lib/PlayerRemote.cpp b/lib/PlayerRemote.cpp index 4b23b01c5..aaa0d904d 100644 --- a/lib/PlayerRemote.cpp +++ b/lib/PlayerRemote.cpp @@ -25,8 +25,6 @@ along with Ginga. If not, see . */ GINGA_NAMESPACE_BEGIN -#define REMOTE_PLAYER_ACT_JSON "{ \"action\" : \"%s\", \"delay\" : \"%d\"}" - /** * @brief Creates PlayerRemote * @param fmt Formatter @@ -50,7 +48,7 @@ PlayerRemote::doSetProperty (Property code, const string &name, if (_url != nullptr) g_free (_url); _url = g_strdup_printf ("http://%s/scene/nodes/%s", value.c_str (), - _media->getId ().c_str()); + _media->getId ().c_str ()); break; default: return Player::doSetProperty (code, name, value); @@ -61,15 +59,29 @@ PlayerRemote::doSetProperty (Property code, const string &name, PlayerRemote::~PlayerRemote () { g_free (_url); + g_object_unref (_session); } -bool +static void +ws_action_callback (SoupSession *session, SoupMessage *msg, + gpointer user_data) +{ + string url; + auto player = (PlayerRemote *) user_data; + url = player->getProperty ("remotePlayerBaseURL"); + if (!msg->status_code == SOUP_STATUS_OK) + WARNING ("Failed to perform request %s", url.c_str ()); + TRACE_SOUP_REQ_MSG (msg); +} + +void PlayerRemote::sendAction (const string &action) { guint status; SoupMessage *msg; char *body; + g_assert_nonnull (_url); if (!_sessionStarted) { _session @@ -77,50 +89,44 @@ PlayerRemote::sendAction (const string &action) SOUP_TYPE_CONTENT_SNIFFER, NULL); _sessionStarted = true; } - g_assert_nonnull (_url); msg = soup_message_new ("POST", _url); - body = g_strdup_printf (REMOTE_PLAYER_ACT_JSON, action.c_str (), 0); + body = g_strdup_printf (REMOTE_PLAYER_JSON_ACT, action.c_str (), 0); soup_message_set_request (msg, "application/json", SOUP_MEMORY_COPY, body, strlen (body)); - status = soup_session_send_message (_session, msg); - if(!status){ - WARNING ("Failed perform request %s", _url); - } + soup_session_queue_message (_session, msg, ws_action_callback, this); g_free (msg); g_free (body); - g_free (msg); - return true; } void PlayerRemote::start () { - g_assert (sendAction ("start")); + sendAction ("start"); } void PlayerRemote::startPreparation () { - g_assert (sendAction ("prepare")); + sendAction ("prepare"); } void PlayerRemote::stop () { - g_assert (sendAction ("stop")); + sendAction ("stop"); } void PlayerRemote::pause () { - g_assert (sendAction ("pause")); + sendAction ("pause"); } void PlayerRemote::resume () { - g_assert (sendAction ("resume")); + sendAction ("resume"); } GINGA_NAMESPACE_END \ No newline at end of file diff --git a/lib/PlayerRemote.h b/lib/PlayerRemote.h index 5f6a73590..00f7e67d5 100644 --- a/lib/PlayerRemote.h +++ b/lib/PlayerRemote.h @@ -25,6 +25,13 @@ GINGA_NAMESPACE_BEGIN class WebServices; +#define REMOTE_PLAYER_JSON_ACT \ + "{\ + \"action\" : \"%s\",\ + \"delay\" : \"%d\"\ + }" + + class PlayerRemote : public Player { public: @@ -38,7 +45,7 @@ class PlayerRemote : public Player protected: bool doSetProperty (Property, const string &, const string &); - bool sendAction (const string &); + void sendAction (const string &); char * _url; bool _sessionStarted; WebServices *_ws; diff --git a/lib/WebServices.cpp b/lib/WebServices.cpp index 09898c47a..5496575e7 100644 --- a/lib/WebServices.cpp +++ b/lib/WebServices.cpp @@ -8,15 +8,6 @@ #include #include -#define WS_ROURTE_LOC "/location" -#define WS_ROURTE_RPLAYER "/remote-mediaplayer" -#define WS_ROURTE_APPS "/current-service/apps/" -#define WS_PORT 44642 -#define SSDP_UUID "uuid:b16f8e7e-8050-11eb-8036-00155dfe4f40" -#define SSDP_DEVICE "upnp:rootdevice" -#define SSDP_NAME "TeleMidia GingaCCWebServices" -#define SSDP_USN "urn:schemas-sbtvd-org:service:GingaCCWebServices:1" - #define WS_ADD_ROUTE(ws, r, f) \ G_STMT_START \ { \ @@ -24,19 +15,6 @@ } \ G_STMT_END -#define TRACE_RQ_HEADERS(msg) \ - G_STMT_START \ - { \ - SoupMessageHeadersIter it; \ - const gchar *name; \ - const gchar *value; \ - soup_message_headers_iter_init (&it, msg->request_headers); \ - while (soup_message_headers_iter_next (&it, &name, &value)) \ - TRACE ("req %s: %s", name, value); \ - TRACE ("req body:\n%s\n", name, value, msg->request_body->data); \ - } \ - G_STMT_END - /** * @brief Creates a new WebServices. * @return New #WebServices. @@ -44,19 +22,31 @@ WebServices::WebServices (Formatter *fmt) { _formatter = fmt; - _started = false; + _resource_group = nullptr; + _client = nullptr; + _ws = nullptr; + _state = WS_STATE_STOPPED; } WebServices::~WebServices () { g_object_unref (_resource_group); g_object_unref (_client); + g_object_unref (_ws); +} + +bool +WebServices::stop () +{ + _state = WS_STATE_STOPPED; + gssdp_resource_group_set_available (_resource_group, FALSE); + return true; } bool WebServices::machMediaThenSetPlayerRemote (PlayerRemoteData &data) { - if (!getCurrentDocument ()) + if (!_formatter->getDocument ()) return false; auto mrts = _formatter->getDocument ()->getMediasRemote (); @@ -89,16 +79,16 @@ WebServices::machMediaThenSetPlayerRemote (PlayerRemoteData &data) return found; } -bool -WebServices::isStarted () +WebServicesState +WebServices::getState () { - return _started; + return _state; } -Document * -WebServices::getCurrentDocument () +Formatter * +WebServices::getFormatter () { - return _formatter->getDocument (); + return _formatter; } static void @@ -122,16 +112,15 @@ ws_loc_callback (SoupServer *server, SoupMessage *msg, const char *path, soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, "text/plan", SOUP_MEMORY_COPY, NULL, 0); - // Add GingaCC-WS headers - value = g_strdup_printf ("http://%s:%d", ws->_host_addr, WS_PORT); + // add GingaCC-Server-* headers + value = g_strdup_printf ("http://%s:%d", ws->host_addr, WS_PORT); soup_message_headers_append (msg->response_headers, "GingaCC-Server-BaseURL", value); - - value = g_strdup_printf ("https://%s:%d", ws->_host_addr, WS_PORT); + g_free (value); + value = g_strdup_printf ("https://%s:%d", ws->host_addr, WS_PORT); soup_message_headers_append (msg->response_headers, "GingaCC-Server-SecureBaseURL", value); g_free (value); - soup_message_headers_append ( msg->response_headers, "GingaCC-Server-PairingMethods", "qcode,kex"); } @@ -148,7 +137,7 @@ ws_remoteplayer_callback (SoupServer *server, SoupMessage *msg, WebServices *ws = (WebServices *) user_data; bool status; - TRACE_RQ_HEADERS (msg); + TRACE_SOUP_REQ_MSG (msg); if (!reader->parse (msg->request_body->data, msg->request_body->data + msg->request_body->length, @@ -183,19 +172,21 @@ ws_apps_callback (SoupServer *server, SoupMessage *msg, const char *path, string action, interface, value; Object *node; Event *evt; + Document *doc; const char *target = path + strlen (WS_ROURTE_APPS); gchar **params = g_strsplit (target, "/", 3); + + doc = ws->getFormatter ()->getDocument (); + g_assert_nonnull (doc); const char *appId = params[0]; const char *docId = params[1]; const char *nodeId = params[2]; - - if (!ws->getCurrentDocument () || !strlen (appId) || !strlen (docId) - || !strlen (nodeId)) + if (!strlen (appId) || !strlen (docId) || !strlen (nodeId)) goto fail; TRACE ("request %s: app=%s, docId=%s nodeId=%s", WS_ROURTE_APPS, appId, docId, nodeId); - TRACE_RQ_HEADERS (msg); + TRACE_SOUP_REQ_MSG (msg); if (!reader->parse (msg->request_body->data, msg->request_body->data + msg->request_body->length, @@ -208,34 +199,35 @@ ws_apps_callback (SoupServer *server, SoupMessage *msg, const char *path, interface = root["interface"].asString (); value = root["value"].asString (); - node = ws->getCurrentDocument ()->getObjectById (nodeId); - g_assert_nonnull (node); + node = doc->getObjectById (nodeId); + g_strfreev (params); + + if (!node) + goto fail; - if (xstrcasecmp (action, "select") == 0) + if (action == "select") { evt = node->getSelectionEvent (value); - ws->getCurrentDocument ()->evalAction (evt, Event::START); - ws->getCurrentDocument ()->evalAction (evt, Event::STOP); + doc->evalAction (evt, Event::START); + doc->evalAction (evt, Event::STOP); } - else if (xstrcasecmp (action, "lookAt") == 0) + else if (action == "lookAt") { evt = node->getLookAtEvent ("@lambda"); - ws->getCurrentDocument ()->evalAction (evt, Event::START); + doc->evalAction (evt, Event::START); } - else if (xstrcasecmp (action, "lookAway") == 0) + else if (action == "lookAway") { evt = node->getLookAtEvent ("@lambda"); - ws->getCurrentDocument ()->evalAction (evt, Event::STOP); + doc->evalAction (evt, Event::STOP); } else - { - goto fail; - } + goto fail; soup_message_set_status (msg, SOUP_STATUS_OK); + return; fail: - g_strfreev (params); soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); } @@ -256,10 +248,10 @@ WebServices::start () } _resource_group = gssdp_resource_group_new (_client); g_assert (_resource_group); - _host_addr = gssdp_client_get_host_ip (_client); + host_addr = gssdp_client_get_host_ip (_client); // ssdp avaliable - location = g_strdup_printf ("http://%s:%d/location", _host_addr, WS_PORT); + location = g_strdup_printf ("http://%s:%d/location", host_addr, WS_PORT); gssdp_resource_group_add_resource_simple (_resource_group, SSDP_USN, SSDP_USN, location); g_free (location); @@ -284,7 +276,7 @@ WebServices::start () WS_ADD_ROUTE (_ws, WS_ROURTE_APPS, ws_apps_callback); WS_ADD_ROUTE (_ws, nullptr, ws_null_callback); - _started = true; + _state = WS_STATE_STARTED; return true; fail: g_error_free (error); diff --git a/lib/WebServices.h b/lib/WebServices.h index 982766433..6d40feff2 100644 --- a/lib/WebServices.h +++ b/lib/WebServices.h @@ -21,12 +21,22 @@ along with Ginga. If not, see . */ #include "aux-ginga.h" #include #include +#include "Event.h" GINGA_NAMESPACE_BEGIN class Formatter; class Document; class Media; +/** + * @brief WevServices states. + */ +typedef enum +{ + WS_STATE_STARTED, + WS_STATE_STOPPED, +} WebServicesState; + /** * @brief PlayerRemoteData. */ @@ -38,6 +48,16 @@ typedef struct list recognizedableEvents; } PlayerRemoteData; + +#define WS_ROURTE_LOC "/location" +#define WS_ROURTE_RPLAYER "/remote-mediaplayer" +#define WS_ROURTE_APPS "/current-service/apps/" +#define WS_PORT 44642 +#define SSDP_UUID "uuid:b16f8e7e-8050-11eb-8036-00155dfe4f40" +#define SSDP_DEVICE "upnp:rootdevice" +#define SSDP_NAME "TeleMidia GingaCCWebServices" +#define SSDP_USN "urn:schemas-sbtvd-org:service:GingaCCWebServices:1" + /** * @brief WebSercices. */ @@ -48,14 +68,15 @@ class WebServices explicit WebServices (Formatter *); ~WebServices (); bool start (); - bool isStarted (); - const char *_host_addr; + bool stop (); + WebServicesState getState (); bool machMediaThenSetPlayerRemote (PlayerRemoteData &); - Document *getCurrentDocument (); + Formatter *getFormatter (); + const char *host_addr; private: Formatter *_formatter; - bool _started; + WebServicesState _state; map _playerMap; GSSDPClient *_client; GSSDPResourceGroup *_resource_group; diff --git a/lib/aux-ginga.h b/lib/aux-ginga.h index 80be9db2c..9cad6c8c6 100644 --- a/lib/aux-ginga.h +++ b/lib/aux-ginga.h @@ -113,6 +113,19 @@ string __ginga_strfunc (const string &); #define ERROR_NOT_IMPLEMENTED(fmt, ...)\ ERROR ("not implemented: " fmt, ## __VA_ARGS__) +#define TRACE_SOUP_REQ_MSG(msg) \ + G_STMT_START \ + { \ + SoupMessageHeadersIter it; \ + const gchar *name; \ + const gchar *value; \ + soup_message_headers_iter_init (&it, msg->request_headers); \ + while (soup_message_headers_iter_next (&it, &name, &value)) \ + TRACE ("request header %s: %s", name, value); \ + TRACE ("request body:\n%s\n", name, value, msg->request_body->data); \ + } \ + G_STMT_END + // Internal types. typedef GdkRGBA Color; typedef GdkRectangle Rect; diff --git a/lib/ginga.h b/lib/ginga.h index 890c86329..2f8223ba2 100644 --- a/lib/ginga.h +++ b/lib/ginga.h @@ -53,7 +53,7 @@ struct GingaOptions bool debug; /// @brief Whether to enable debug mode. - bool webservice; + bool webservices; /// @brief Whether to enable experimental features. bool experimental; @@ -89,7 +89,6 @@ class Ginga virtual GingaState getState () = 0; virtual bool start (const std::string &path, std::string *errmsg) = 0; virtual bool stop () = 0; - virtual bool startWebServices () = 0; virtual void resize (int width, int height) = 0; virtual void redraw (cairo_t *cr) = 0; diff --git a/src/ginga.cpp b/src/ginga.cpp index 78f009864..a1fb634b6 100644 --- a/src/ginga.cpp +++ b/src/ginga.cpp @@ -43,7 +43,7 @@ static Ginga *GINGA = nullptr; static gboolean opt_debug = FALSE; // toggle debug static gboolean opt_experimental = FALSE; // toggle experimental stuff static gboolean opt_fullscreen = FALSE; // toggle fullscreen-mode -static gboolean opt_ws = FALSE; // toggle webservices-only-mode +static gboolean opt_webservices = FALSE; // toggle webservices-only-mode static gboolean opt_opengl = FALSE; // toggle OpenGL backend static string opt_background = ""; // background color static gint opt_width = 800; // initial window width @@ -101,7 +101,7 @@ static GOptionEntry options[] "Enable debugging", NULL }, { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &opt_fullscreen, "Enable full-screen mode", NULL }, - { "ws", 'w', 0, G_OPTION_ARG_NONE, &opt_ws, + { "ws", 'w', 0, G_OPTION_ARG_NONE, &opt_webservices, "Enable WebService and turn file param optional.", NULL }, { "opengl", 'g', 0, G_OPTION_ARG_NONE, &opt_opengl, "Use OpenGL backend", NULL }, @@ -343,7 +343,7 @@ main (int argc, char **argv) _exit (0); } - if (!opt_ws && saved_argc < 2) + if (!opt_webservices && saved_argc < 2) { usage_error ("Missing file operand"); _exit (0); @@ -406,7 +406,7 @@ main (int argc, char **argv) opts.width = opt_width; opts.height = opt_height; opts.debug = opt_debug; - opts.webservice = opt_ws; + opts.webservices = opt_webservices; opts.experimental = opt_experimental; opts.opengl = opt_opengl; opts.background = string (opt_background); @@ -414,15 +414,15 @@ main (int argc, char **argv) g_assert_nonnull (GINGA); int fail_count = 0; - // Run only GingaCC-WebServices - if (opt_ws && saved_argc < 2) - { - GINGA->startWebServices (); - GMainLoop * main_loop = g_main_loop_new (NULL, FALSE); - g_main_loop_run (main_loop); - g_main_loop_unref (main_loop); - goto done; - } + // // Run only GingaCC-WebServices + // if (opt_webservices && saved_argc < 2) + // { + // GINGA->startWebServices (); + // GMainLoop * main_loop = g_main_loop_new (NULL, FALSE); + // g_main_loop_run (main_loop); + // g_main_loop_unref (main_loop); + // goto done; + // } // Run each NCL file, one after another. for (int i = 1; i < saved_argc; i++) diff --git a/tests/test-Ginga-getOptions.cpp b/tests/test-Ginga-getOptions.cpp index 10c6c7427..3c983d0e2 100644 --- a/tests/test-Ginga-getOptions.cpp +++ b/tests/test-Ginga-getOptions.cpp @@ -27,7 +27,7 @@ main (void) opts.height = 20; opts.debug = false; opts.experimental = false; - opts.webservice = false; + opts.webservices = false; opts.opengl = false; opts.background = "green"; Ginga *ginga = Ginga::create (&opts); @@ -37,7 +37,7 @@ main (void) g_assert (out->width == opts.width); g_assert (out->height == opts.height); g_assert (out->debug == opts.debug); - g_assert (out->webservice == opts.webservice); + g_assert (out->webservices == opts.webservices); g_assert (out->experimental == opts.experimental); g_assert (out->opengl == opts.opengl); g_assert (out->background == opts.background); diff --git a/tests/test-WebServices.cpp b/tests/test-WebServices.cpp new file mode 100644 index 000000000..8a527cbef --- /dev/null +++ b/tests/test-WebServices.cpp @@ -0,0 +1,215 @@ +/* Copyright (C) 2006-2018 PUC-Rio/Laboratorio TeleMidia + +This file is part of Ginga (Ginga-NCL). + +Ginga is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Ginga 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. See the GNU General Public +License for more details. + +You should have received a copy of the GNU General Public License +along with Ginga. If not, see . */ + +#include "tests.h" +#include "WebServices.h" +#include "PlayerRemote.h" +#include + +Formatter *fmt; + +static void +cb_endloop (SoupSession *session, SoupMessage *msg, gpointer loop) +{ + g_assert (msg->status_code == SOUP_STATUS_OK); + g_main_loop_quit ((GMainLoop *) loop); +} + +static void +cb_test_location_endloop (SoupSession *session, SoupMessage *msg, + gpointer loop) +{ + SoupMessageHeadersIter it; + const gchar *name; + const gchar *value; + char *url1; + char *url2; + const char *host_addr = fmt->getWebServices ()->host_addr; + SoupMessageHeaders *hdrs; + + g_assert (msg->status_code == SOUP_STATUS_OK); + + url1 = g_strdup_printf ("http://%s:%d", host_addr, WS_PORT); + url2 = g_strdup_printf ("https://%s:%d", host_addr, WS_PORT); + + hdrs = msg->response_headers; + g_assert_cmpstr ( + soup_message_headers_get_one (hdrs, "GingaCC-Server-BaseURL"), ==, + url1); + g_assert_cmpstr ( + soup_message_headers_get_one (hdrs, "GingaCC-Server-SecureBaseURL"), + ==, url2); + g_assert_cmpstr ( + soup_message_headers_get_one (hdrs, "GingaCC-Server-PairingMethods"), + ==, "qcode,kex"); + g_free (url1); + g_free (url2); + g_main_loop_quit ((GMainLoop *) loop); +} + +void +test_ws_get_location (GMainLoop *loop) +{ + SoupMessage *msg; + SoupSession *session; + gchar *url; + + session = soup_session_new (); + g_assert_nonnull (session); + url = g_strdup_printf ("http://localhost:%d%s", WS_PORT, WS_ROURTE_LOC); + g_assert_nonnull (url); + msg = soup_message_new ("GET", url); + g_assert_nonnull (msg); + soup_session_queue_message (session, msg, cb_test_location_endloop, loop); + + g_free (url); + g_object_unref (session); +} + +void +test_ws_post_action (const char *node, const char *action, GMainLoop *loop) +{ + guint status; + SoupMessage *msg; + SoupSession *session; + gchar *body, *url; + + session = soup_session_new (); + g_assert_nonnull (session); + url = g_strdup_printf ("http://localhost:%d%s1/docId/%s", WS_PORT, + WS_ROURTE_APPS, node); + g_assert_nonnull (url); + msg = soup_message_new ("POST", url); + g_assert_nonnull (msg); + body = g_strdup_printf (REMOTE_PLAYER_JSON_ACT, action, 0); + g_assert_nonnull (body); + soup_message_set_request (msg, "application/json", SOUP_MEMORY_COPY, body, + strlen (body)); + soup_session_queue_message (session, msg, cb_endloop, loop); + + g_free (url); + g_free (body); + g_object_unref (session); +} + +int +main (int argc, char **argv) +{ + Document *doc; + tests_parse_and_start (&fmt, &doc, "\ +\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +"); + Formatter::setOptionDebug (fmt, "debug", true); + Formatter::setOptionWebServices (fmt, "webservices", true); + + GMainLoop *loop; + loop = g_main_loop_new (NULL, FALSE); + + // test get location + test_ws_get_location (loop); + g_main_loop_run (loop); + + // test post actions: lookAt and lookAway + { + Context *body = cast (Context *, doc->getRoot ()); + g_assert_nonnull (body); + Event *body_lambda = body->getLambda (); + g_assert_nonnull (body_lambda); + + Media *m1 = cast (Media *, doc->getObjectById ("m1")); + g_assert_nonnull (m1); + Event *m1_lambda = m1->getLambda (); + g_assert_nonnull (m1_lambda); + Media *m2 = cast (Media *, doc->getObjectById ("m2")); + g_assert_nonnull (m2); + Event *m2_lambda = m2->getLambda (); + g_assert_nonnull (m2_lambda); + Media *m3 = cast (Media *, doc->getObjectById ("m3")); + g_assert_nonnull (m3); + Event *m3_lambda = m3->getLambda (); + g_assert_nonnull (m3_lambda); + + // when document is started, only the body_lambda is OCCURING + g_assert_cmpint ((body_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m1_lambda)->getState (), ==, Event::SLEEPING); + g_assert_cmpint ((m2_lambda)->getState (), ==, Event::SLEEPING); + g_assert_cmpint ((m3_lambda)->getState (), ==, Event::SLEEPING); + + // when advance time, m1_lambda is OCCURRING + (fmt)->sendTick (0, 0, 0); + g_assert_cmpint ((body_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m1_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m2_lambda)->getState (), ==, Event::SLEEPING); + g_assert_cmpint ((m3_lambda)->getState (), ==, Event::SLEEPING); + + // START is done + Event *evtOnLookAt = m1->getLookAtEvent ("@lambda"); + g_assert_nonnull (evtOnLookAt); + + // lookAt + test_ws_post_action ("m1", "lookAt", loop); + g_main_loop_run (loop); + + // after START, m1_onLooAt m2 OCCURRING + (fmt)->sendTick (0, 0, 0); + g_assert_cmpint ((body_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m1_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m2_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m3_lambda)->getState (), ==, Event::SLEEPING); + + // lookAway + test_ws_post_action ("m1", "lookAway", loop); + g_main_loop_run (loop); + + // after START, m1_onLooAway m3 are OCCURRING + (fmt)->sendTick (0, 0, 0); + g_assert_cmpint ((body_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m1_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m2_lambda)->getState (), ==, Event::OCCURRING); + g_assert_cmpint ((m3_lambda)->getState (), ==, Event::OCCURRING); + } + + g_main_loop_unref (loop); + delete fmt; + exit (EXIT_SUCCESS); +}