summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2013-02-25 12:28:05 (GMT)
committerStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2013-02-25 12:28:05 (GMT)
commit49c3c43d18c9cce6de305aae77cc8bd839506129 (patch)
treecfc4ea84416c76e8bbe3e27d2918321115b61e24
parent47956a35d11495f2ebf6988c7f9d9dffe0bd3a4b (diff)
downloaduscxml-49c3c43d18c9cce6de305aae77cc8bd839506129.zip
uscxml-49c3c43d18c9cce6de305aae77cc8bd839506129.tar.gz
uscxml-49c3c43d18c9cce6de305aae77cc8bd839506129.tar.bz2
Introduced postpone element and reorganized http request representation as events
-rw-r--r--CMakeLists.txt80
-rw-r--r--README.md37
-rw-r--r--apps/samples/vrml-server.scxml103
-rw-r--r--config.h.in1
-rw-r--r--contrib/cmake/FindFFMPEG.cmake79
-rw-r--r--docs/BUILDING.md231
-rw-r--r--docs/EXTENSIONS.md37
-rw-r--r--src/uscxml/Factory.cpp16
-rw-r--r--src/uscxml/Interpreter.cpp57
-rw-r--r--src/uscxml/Interpreter.h25
-rw-r--r--src/uscxml/Message.h3
-rw-r--r--src/uscxml/URL.cpp1
-rw-r--r--src/uscxml/URL.cpp.old519
-rw-r--r--src/uscxml/URL.h.old154
-rw-r--r--src/uscxml/concurrency/BlockingQueue.h8
-rw-r--r--src/uscxml/concurrency/tinythread.cpp20
-rw-r--r--src/uscxml/concurrency/tinythread.h5
-rw-r--r--src/uscxml/plugins/element/postpone/PostponeElement.cpp97
-rw-r--r--src/uscxml/plugins/element/postpone/PostponeElement.h62
-rw-r--r--src/uscxml/plugins/element/response/ResponseElement.cpp38
-rw-r--r--src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp48
-rw-r--r--src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h40
-rw-r--r--src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp33
-rw-r--r--src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.cpp300
-rw-r--r--src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.h30
-rw-r--r--src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp13
-rw-r--r--src/uscxml/plugins/invoker/http/HTTPServletInvoker.h2
-rw-r--r--src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp7
-rw-r--r--src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h3
-rw-r--r--src/uscxml/plugins/ioprocessor/basichttp/libevent/EventIOProcessor.cpp19
-rw-r--r--src/uscxml/server/HTTPServer.cpp119
-rw-r--r--src/uscxml/server/HTTPServer.h16
-rw-r--r--test/samples/uscxml/test-osgconvert.scxml40
-rw-r--r--test/samples/uscxml/test-postpone-element.scxml41
-rw-r--r--test/samples/uscxml/test-response-element.scxml4
35 files changed, 1447 insertions, 841 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0b655db..d4697a7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -430,7 +430,7 @@ file(GLOB_RECURSE SAMPLE_INVOKER
src/uscxml/plugins/invoker/sample/*.cpp
src/uscxml/plugins/invoker/sample/*.h
)
-source_group("Invoker" FILES ${SAMPLE_INVOKER})
+source_group("Invoker\\sample" FILES ${SAMPLE_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
invoker_sample SHARED
@@ -448,7 +448,7 @@ file(GLOB_RECURSE LIBEVENT_IOPROCESSOR
src/uscxml/plugins/ioprocessor/basichttp/*.cpp
src/uscxml/plugins/ioprocessor/basichttp/*.h
)
-source_group("IOProcessor" FILES ${LIBEVENT_IOPROCESSOR})
+source_group("IOProcessor\\basichttp" FILES ${LIBEVENT_IOPROCESSOR})
if (BUILD_AS_PLUGINS)
add_library(
ioprocessor_basichttp SHARED
@@ -466,7 +466,7 @@ file(GLOB_RECURSE FETCH_ELEMENT
src/uscxml/plugins/element/fetch/*.cpp
src/uscxml/plugins/element/fetch/*.h
)
-source_group("Element" FILES ${FETCH_ELEMENT})
+source_group("Element\\fetch" FILES ${FETCH_ELEMENT})
if (BUILD_AS_PLUGINS)
add_library(
element_fetch SHARED
@@ -478,13 +478,31 @@ else()
endif()
+# Postpone element
+
+file(GLOB_RECURSE POSTPONE_ELEMENT
+ src/uscxml/plugins/element/postpone/*.cpp
+ src/uscxml/plugins/element/postpone/*.h
+)
+source_group("Element\\postpone" FILES ${POSTPONE_ELEMENT})
+if (BUILD_AS_PLUGINS)
+ add_library(
+ element_postpone SHARED
+ ${POSTPONE_ELEMENT})
+ target_link_libraries(element_postpone uscxml)
+ set_target_properties(element_postpone PROPERTIES FOLDER "Plugin Element")
+else()
+ list (APPEND USCXML_FILES ${POSTPONE_ELEMENT})
+endif()
+
+
# Response element
file(GLOB_RECURSE RESPONSE_ELEMENT
src/uscxml/plugins/element/response/*.cpp
src/uscxml/plugins/element/response/*.h
)
-source_group("Element" FILES ${RESPONSE_ELEMENT})
+source_group("Element\\response" FILES ${RESPONSE_ELEMENT})
if (BUILD_AS_PLUGINS)
add_library(
element_response SHARED
@@ -502,7 +520,7 @@ file(GLOB_RECURSE DIRMON_INVOKER
src/uscxml/plugins/invoker/filesystem/dirmon/*.cpp
src/uscxml/plugins/invoker/filesystem/dirmon/*.h
)
-source_group("Invoker" FILES ${DIRMON_INVOKER})
+source_group("Invoker\\dirmon" FILES ${DIRMON_INVOKER})
include_directories(${PROJECT_SOURCE_DIR}/src/uscxml/plugins/invoker/filesystem/dirmon/)
if (BUILD_AS_PLUGINS)
add_library(
@@ -523,7 +541,7 @@ if (SQLITE3_FOUND)
src/uscxml/plugins/invoker/sqlite3/*.cpp
src/uscxml/plugins/invoker/sqlite3/*.h
)
- source_group("Invoker" FILES ${SQLITE3_INVOKER})
+ source_group("Invoker\\sqlite" FILES ${SQLITE3_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
invoker_sqlite3 SHARED
@@ -536,6 +554,28 @@ if (SQLITE3_FOUND)
endif()
+# ffmpeg invoker
+
+find_package(FFMPEG)
+if (FFMPEG_FOUND)
+ include_directories(${FFMPEG_INCLUDE_DIR})
+ file(GLOB_RECURSE FFMPEG_INVOKER
+ src/uscxml/plugins/invoker/ffmpeg/*.cpp
+ src/uscxml/plugins/invoker/ffmpeg/*.h
+ )
+ source_group("Invoker\\ffmpeg" FILES ${FFMPEG_INVOKER})
+ if (BUILD_AS_PLUGINS)
+ add_library(
+ invoker_ffmpeg SHARED
+ ${FFMPEG_INVOKER})
+ target_link_libraries(invoker_ffmpeg uscxml)
+ set_target_properties(invoker_ffmpeg PROPERTIES FOLDER "Plugin Invoker")
+ else()
+ list (APPEND USCXML_FILES ${FFMPEG_INVOKER})
+ endif()
+endif()
+
+
# JavaScriptCore ecmascript datamodel
if (APPLE AND IOS AND OFF)
FIND_LIBRARY(JSC_LIBRARY JavaScriptCore)
@@ -544,12 +584,12 @@ if (APPLE AND IOS AND OFF)
src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/*.cpp
src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/*.h
)
- source_group("Datamodel" FILES ${JSC_DATAMODEL})
+ source_group("Datamodel\\jsc" FILES ${JSC_DATAMODEL})
file(GLOB_RECURSE JSC_DOM
src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/dom/*.cpp
src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/dom/*.h
)
- source_group("DOM" FILES ${JSC_DOM})
+ source_group("Datamodel\\DOM" FILES ${JSC_DOM})
if (BUILD_AS_PLUGINS)
add_library(
datamodel_jsc SHARED
@@ -578,7 +618,7 @@ endif()
src/uscxml/plugins/datamodel/ecmascript/v8/*.cpp
src/uscxml/plugins/datamodel/ecmascript/v8/*.h
)
- source_group("Datamodel" FILES ${V8_DATAMODEL})
+ source_group("Datamodel\\v8" FILES ${V8_DATAMODEL})
file(GLOB_RECURSE V8_DOM
# ${PROJECT_SOURCE_DIR}/contrib/dom/scripts/gen/*.cpp
# ${PROJECT_SOURCE_DIR}/contrib/dom/scripts/gen/*.h
@@ -589,7 +629,7 @@ endif()
# list(APPEND V8_DOM "src/uscxml/plugins/datamodel/ecmascript/v8/dom/V8DOM.cpp")
# list(APPEND V8_DOM "src/uscxml/plugins/datamodel/ecmascript/v8/dom/V8DOM.h")
- source_group("DOM" FILES ${V8_DOM})
+ source_group("Datamodel\\DOM" FILES ${V8_DOM})
if (BUILD_AS_PLUGINS)
add_library(
@@ -624,7 +664,7 @@ if (SWI_FOUND)
src/uscxml/plugins/datamodel/prolog/swi/*.cpp
src/uscxml/plugins/datamodel/prolog/swi/*.h
)
- source_group("Datamodel" FILES ${SWI_DATAMODEL})
+ source_group("Datamodel\\swi" FILES ${SWI_DATAMODEL})
if (BUILD_AS_PLUGINS)
add_library(
datamodel_swi SHARED
@@ -654,7 +694,7 @@ if (UMUNDO_FOUND)
file(GLOB_RECURSE UMUNDO_INVOKER
src/uscxml/plugins/invoker/umundo/*.cpp
src/uscxml/plugins/invoker/umundo/*.h)
- source_group("Invoker" FILES ${UMUNDO_INVOKER})
+ source_group("Invoker\\umundo" FILES ${UMUNDO_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
invoker_umundo SHARED
@@ -676,7 +716,7 @@ add_definitions("-DUMUNDO_STATIC")
file(GLOB_RECURSE USCXML_INVOKER
src/uscxml/plugins/invoker/scxml/*.cpp
src/uscxml/plugins/invoker/scxml/*.h)
-source_group("Invoker" FILES ${USCXML_INVOKER})
+source_group("Invoker\\uscxml" FILES ${USCXML_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
invoker_uscxml SHARED
@@ -691,19 +731,19 @@ endif()
# HTTP server invoker
-file(GLOB_RECURSE HTTPSERVER_INVOKER
+file(GLOB_RECURSE HTTPSERVLET_INVOKER
src/uscxml/plugins/invoker/http/*.cpp
src/uscxml/plugins/invoker/http/*.h)
-source_group("Invoker" FILES ${HTTPSERVER_INVOKER})
+source_group("Invoker\\httpservlet" FILES ${HTTPSERVLET_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
invoker_httpserver SHARED
- ${HTTPSERVER_INVOKER})
+ ${HTTPSERVLET_INVOKER})
target_link_libraries(invoker_httpserver
uscxml)
set_target_properties(invoker_httpserver PROPERTIES FOLDER "Plugin Invoker")
else()
- list (APPEND USCXML_FILES ${HTTPSERVER_INVOKER})
+ list (APPEND USCXML_FILES ${HTTPSERVLET_INVOKER})
endif()
@@ -713,7 +753,7 @@ file(GLOB_RECURSE HEARTBEAT_INVOKER
src/uscxml/plugins/invoker/heartbeat/*.cpp
src/uscxml/plugins/invoker/heartbeat/*.h
)
-source_group("Invoker" FILES ${HEARTBEAT_INVOKER})
+source_group("Invoker\\heartbeat" FILES ${HEARTBEAT_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
invoker_heartbeat SHARED
@@ -743,7 +783,7 @@ if (OPENSCENEGRAPH_FOUND AND OPENGL_FOUND)
file(GLOB_RECURSE OPENSCENEGRAPH_INVOKER
src/uscxml/plugins/invoker/graphics/openscenegraph/*.cpp
src/uscxml/plugins/invoker/graphics/openscenegraph/*.h)
- source_group("Invoker" FILES ${OPENSCENEGRAPH_INVOKER})
+ source_group("Invoker\\scenegraph" FILES ${OPENSCENEGRAPH_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
@@ -775,7 +815,7 @@ if (MILES_FOUND AND OFF)
include_directories(${OPENAL_INCLUDE_DIR})
file(GLOB_RECURSE MILES_INVOKER src/uscxml/invoker/modality/miles/*.cpp src/uscxml/invoker/modality/miles/*.h)
- source_group("Invoker" FILES ${MILES_INVOKER})
+ source_group("Invoker\\miles" FILES ${MILES_INVOKER})
if (BUILD_AS_PLUGINS)
add_library(
diff --git a/README.md b/README.md
index 3db0ea2..c78adea 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,36 @@
# uSCXML ReadMe
-uSCXML is a SCXML interpreter written in platform independent C/C++. It is still in a rather early stage but mostly
-feature-complete as far as the W3C SCXML draft specifies.
+uSCXML is a SCXML interpreter written in C/C++. It is still in a rather early stage but mostly
+feature-complete as far as the W3C SCXML draft specifies. It runs on most <b>Linux</b>,
+<b>Windows</b> and <b>MacOSX</b>, each 32- as well as 64Bits.
- * ECMAScript datamodel using Google's v8 implementation
- * Full DOM implementation via Arabica
- * Integrates with uMundo to provide publish / subscribe
+There is no technical reason for it not to run on iOS and Android as well, but we did not yet setup
+the respective build-process.
+ * <b>Datamodels</b>
+ * ECMAScript using Google's v8 and JavaScriptCore (JSC is incomplete)
+ * Prolog using SWI prolog
+ * <b>Invokers</b>
+ * <tt>scxml</tt>: Invoke a nested scxml interpreter
+ * <tt>dirmon</tt>: Watches a directory for changes to files
+ * <tt>scenegraph</tt>: Simplified 3D scenegraphs with custom markup
+ * <tt>heartbeat</tt>: Periodically sends events
+ * <tt>httpservlet</tt>: Sends events for http requests to special paths
+ * <tt>umundo</tt>: Subscribe to channels and publish events
+ * <b>DOM</b>
+ * DOM Core Level 2 + XPath extensions available for ecmascript datamodel
+ * Namespace aware to embed custom markup for special invokers
+ * <b>Communication</b>
+ * Features the standard basichttp io-processor
+ * Can actually respond to HTTP requests with data via &lt;response>
+
+## License
+
+uSCXML itself is distributed under the Simplified BSD license as in, do not sue us and do
+not misrepresent authorship. Please have a look at the licenses of the [libraries we depend
+upon](https://github.com/tklab-tud/uscxml/blob/master/docs/BUILDING.md#build-dependencies) as well.
+
+## Download
+
+We do not yet feature installers. Please download the source and have a look at the [build
+instructions](https://github.com/tklab-tud/umundo/blob/master/docs/BUILDING.md).
diff --git a/apps/samples/vrml-server.scxml b/apps/samples/vrml-server.scxml
index e88be4c..f48008c 100644
--- a/apps/samples/vrml-server.scxml
+++ b/apps/samples/vrml-server.scxml
@@ -1,66 +1,89 @@
<scxml datamodel="ecmascript" name="vrml">
<script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" />
- <datamodel>
- <data id="files">
- {
- "foo" : "bar"
- }
- </data>
- </datamodel>
- <parallel id="main">
+ <script>
+ var files = {};
+ </script>
+ <state id="main">
<!-- Stop processing if no vrml-path was given on command line -->
<transition target="final" cond="_x['args']['vrml-path'] == undefined || _x['args']['vrml-path'].length == 0">
<log expr="'No --vrml-path given'" />
</transition>
-
- <state id="processHttp">
- <state id="wait">
- <transition event="http" target="wait">
- <script>
- dump(_event);
- </script>
- <response status="200" requestexpr="_event.origin">
- <content expr="files" />
- </response>
- </transition>
- </state>
- </state>
-
+
<!-- Stop processing if an error occurred -->
<transition target="final" event="error">
<log expr="'An error occured:'" />
<script>dump(_event);</script>
</transition>
-
- <!-- Start the osgconvert invoker to transform 3D files -->
- <invoke type="osgconvert" id="osgvonvert.osgb">
- <param name="format" expr="'osgb'" />
- <param name="destDir" expr="_x['args']['vrml-path'] + '/processed'" />
- </invoke>
-
+
<!-- Start the directory monitor -->
<invoke type="dirmon" id="dirmon.vrml">
<param name="dir" expr="_x['args']['vrml-path']" />
<param name="recurse" expr="true" />
<param name="suffix" expr="'.wrl'" />
<param name="reportExisting" expr="true" />
- <!-- Send every file to the converter -->
+ <!-- Remember every file -->
<finalize>
- <send target="#_osgvonvert.osgb" event="convert">
- <param name="file" expr="_event.data.file" />
- <param name="name" expr="_event.data.file.name" />
- </send>
+ <script>
+ dump(_event);
+ // use filename without directory as the key
+ var key = _event.data.file.name.substr(_event.data.file.dir.length + 1);
+ files[key] = _event.data;
+ </script>
</finalize>
</invoke>
+ <!-- Start the osgconvert invoker to transform 3D files -->
+ <invoke type="osgconvert" id="osgvonvert.osgb">
+ <param name="threads" expr="2" />
+ </invoke>
+
<!-- Idle here -->
<state id="idle">
- <onentry>
- <script>
- dump(_x['args']);
- </script>
- </onentry>
+ <!-- request for a specific format -->
+ <transition event="http" target="idle" cond="
+ _event.data.pathComponent.length == 3 &amp;&amp;
+ _event.data.pathComponent[0] == 'vrml' &amp;&amp;
+ _event.data.pathComponent[1] in files">
+ <script>dump(_event);</script>
+ <if cond="_event.data.pathComponent[2] in files[_event.data.pathComponent[1]]">
+ <response status="200" requestexpr="_event.origin">
+ <content expr="files[_event.data.pathComponent[1]][_event.data.pathComponent[2]]" />
+ </response>
+ <else>
+ <send target="#_osgvonvert.osgb">
+ <param name="source" expr="files[_event.data.pathComponent[1]].path" />
+ <param name="dest" expr="files[_event.data.pathComponent[1]].path + '.png'" />
+ <param name="format" expr="'png'" />
+ </send>
+ <postpone until="_event.data.pathComponent[2] in files[_event.data.pathComponent[1]]" />
+ </else>
+ </if>
+ </transition>
+
+
+ <!-- request for info on a special file -->
+ <transition event="http" target="idle" cond="
+ _event.data.pathComponent.length == 2 &amp;&amp;
+ _event.data.pathComponent[0] == 'vrml' &amp;&amp;
+ _event.data.pathComponent[1] in files">
+ <script>dump(_event);</script>
+ <response status="200" requestexpr="_event.origin">
+ <content expr="files[_event.data.pathComponent[1]]" />
+ </response>
+ </transition>
+
+ <!-- request for topmost list of all files -->
+ <transition event="http" target="idle" cond="
+ _event.data.pathComponent.length == 1 &amp;&amp;
+ _event.data.pathComponent[0] == 'vrml'
+ ">
+ <script>dump(_event);</script>
+ <response status="200" requestexpr="_event.origin">
+ <content expr="files" />
+ </response>
+ </transition>
+
</state>
- </parallel>
+ </state>
<state id="final" final="true" />
</scxml> \ No newline at end of file
diff --git a/config.h.in b/config.h.in
index ca2a6d4..ca2996f 100644
--- a/config.h.in
+++ b/config.h.in
@@ -56,6 +56,7 @@
#cmakedefine V8_FOUND
#cmakedefine JSC_FOUND
#cmakedefine SWI_FOUND
+#cmakedefine FFMPEG_FOUND
#cmakedefine OPENSCENEGRAPH_FOUND
/** Header files we found */
diff --git a/contrib/cmake/FindFFMPEG.cmake b/contrib/cmake/FindFFMPEG.cmake
new file mode 100644
index 0000000..6be21cd
--- /dev/null
+++ b/contrib/cmake/FindFFMPEG.cmake
@@ -0,0 +1,79 @@
+# - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil)
+# Once done this will define
+#
+# FFMPEG_FOUND - system has ffmpeg or libav
+# FFMPEG_INCLUDE_DIR - the ffmpeg include directory
+# FFMPEG_LIBRARIES - Link these to use ffmpeg
+# FFMPEG_LIBAVCODEC
+# FFMPEG_LIBAVFORMAT
+# FFMPEG_LIBAVUTIL
+#
+# Copyright (c) 2008 Andreas Schneider <mail@cynapses.org>
+# Modified for other libraries by Lasse Kärkkäinen <tronic>
+# Modified for Hedgewars by Stepik777
+#
+# Redistribution and use is allowed according to the terms of the New
+# BSD license.
+#
+
+if (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
+ # in cache already
+ set(FFMPEG_FOUND TRUE)
+else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(_FFMPEG_AVCODEC libavcodec)
+ pkg_check_modules(_FFMPEG_AVFORMAT libavformat)
+ pkg_check_modules(_FFMPEG_AVUTIL libavutil)
+ endif (PKG_CONFIG_FOUND)
+
+ find_path(FFMPEG_AVCODEC_INCLUDE_DIR
+ NAMES libavcodec/avcodec.h
+ PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS} /usr/include /usr/local/include /opt/local/include /sw/include
+ PATH_SUFFIXES ffmpeg libav
+ )
+
+ find_library(FFMPEG_LIBAVCODEC
+ NAMES avcodec
+ PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib
+ )
+
+ find_library(FFMPEG_LIBAVFORMAT
+ NAMES avformat
+ PATHS ${_FFMPEG_AVFORMAT_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib
+ )
+
+ find_library(FFMPEG_LIBAVUTIL
+ NAMES avutil
+ PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib
+ )
+
+ if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT)
+ set(FFMPEG_FOUND TRUE)
+ endif()
+
+ if (FFMPEG_FOUND)
+ set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR})
+
+ set(FFMPEG_LIBRARIES
+ ${FFMPEG_LIBAVCODEC}
+ ${FFMPEG_LIBAVFORMAT}
+ ${FFMPEG_LIBAVUTIL}
+ )
+
+ endif (FFMPEG_FOUND)
+
+ if (FFMPEG_FOUND)
+ if (NOT FFMPEG_FIND_QUIETLY)
+ message(STATUS "Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}")
+ endif (NOT FFMPEG_FIND_QUIETLY)
+ else (FFMPEG_FOUND)
+ if (FFMPEG_FIND_REQUIRED)
+ message(FATAL_ERROR "Could not find libavcodec or libavformat or libavutil")
+ endif (FFMPEG_FIND_REQUIRED)
+ endif (FFMPEG_FOUND)
+
+endif (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
+
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
new file mode 100644
index 0000000..60a3260
--- /dev/null
+++ b/docs/BUILDING.md
@@ -0,0 +1,231 @@
+# Building from Source
+
+The source code is built using CMake, the process of building uscxml is
+essentially the same on every platform:
+
+1. Read the <b>[Platform Notes](#platform-notes)</b> below to prepare your system.
+2. Checkout uscxml into a convenient directory:
+
+ <tt>git clone git://github.com/tklab-tud/uscxml.git</tt>
+
+3. Create a new directory for an *out-of-source* build. I usually create sub-directories
+in <tt>&lt;USCXML_SRC&gt;/build/</tt>.
+4. Run cmake (or ccmake / CMake-GUI) to create the files required by your actual build-system.
+5. Use your actual build-system or development environment to build uscxml.
+6. Read the SCXML draft and have a look at the tests to get started.
+
+If you want to build for another IDE or build-system, just create a new
+*out-of-source* build directory and start over with cmake. To get an idea of
+supported IDEs and build-environments on your platform, type <tt>cmake --help</tt>
+or run the CMake-GUI and look for the *Generators* section at the end of the
+output. Default on Unices is Makefiles.
+
+# Build Dependencies
+
+Overview of the uscxml dependencies. See the [Platform Notes](#platform-notes) for details.
+
+<b>Note:</b> We download pre-compiled versions of most dependencies at cmake configure-time. If you want
+to provide you own libraries, remove them from <tt>&lt;USCXML_SRC&gt;/contrib/prebuilt/</tt> and provide
+your own.
+
+<table>
+ <tr><th>Platform</th><th>Dependency</th><th>Version</th><th>Comment</th></tr>
+ <tr>
+ <td rowspan="10"><b>Everyone</b></td>
+ <td><a href="http://www.cmake.org/cmake/resources/software.html">CMake</a><br />required</td>
+ <td>>=&nbsp;2.8.6</td>
+ <td>The build-system used for uscxml.</td></tr>
+ <tr>
+ <td><a href="http://libevent.org">libevent</a><br />pre-compiled</td>
+ <td>>=&nbsp;2.1.x</td>
+ <td>Event queues with callbacks and the HTTP server.</td></tr>
+ <tr>
+ <td><a href="http://curl.haxx.se">curl</a><br />pre-compiled</td>
+ <td>>=&nbsp;7.29.0</td>
+ <td>URL downloads.</td></tr>
+ <tr>
+ <td><a href="http://code.google.com/p/v8/">v8</a><br />pre-compiled</td>
+ <td>svn checkout</td>
+ <td>ECMAScript datamodel implementation.</td></tr>
+ <tr>
+ <td><a href="http://www.swi-prolog.org">SWI Prolog</a><br />pre-compiled for unices</td>
+ <td>>=&nbsp;6.3.x</td>
+ <td>Prolog datamodel implementation.</td></tr>
+ <tr>
+ <td><a href="http://code.google.com/p/google-glog/">glog</a><br />pre-compiled</td>
+ <td>>=&nbsp;0.3.3</td>
+ <td>Logging library.</td></tr>
+ <tr>
+ <td><a href="https://github.com/jezhiggins/arabica">Arabica</a><br />pre-compiled</td>
+ <td>>=&nbsp;git checkout</td>
+ <td>XML DOM / XPath / XML Events.</td></tr>
+ <tr>
+ <td><a href="http://www.sqlite.org">SQLite</a><br />optional</td>
+ <td>>=&nbsp;3.7.15.2</td>
+ <td>Persistence and sqlite invoker.</td></tr>
+ <tr>
+ <td><a href="http://www.openscenegraph.com">OpenSceneGraph</a><br />optional</td>
+ <td>>=&nbsp;3.1.X</td>
+ <td>3D invokers (scenegraph, osgconvert).</td></tr>
+ <tr>
+ <td><a href="http://www.stack.nl/~dimitri/doxygen/">Doxygen</a><br />recommended</td>
+ <td></td>
+ <td>Used by <tt>make docs</tt> to generate documentation from source comments.</td></tr>
+ </tr>
+ <tr bgcolor="grey"><td bgcolor="#dddddd" colspan="4"></td></tr>
+
+ <tr>
+ <td rowspan="3"><b>Mac OSX</b></td>
+ <td><a href="http://developer.apple.com/xcode/">XCode</a><br />required</td>
+ <td>4.2.1 works</td>
+ <td>Apples SDK with all the toolchains.</td></tr>
+ <tr>
+ <td><a href="http://www.macports.org/">MacPorts</a><br />recommended</td>
+ <td>>= 2.0.3</td>
+ <td>Build system for a wide selection of open-source packages.</td></tr>
+ <tr>
+ <td><a href="http://www.xmlsoft.org">libxml2</a><br />pre-installed</td>
+ <td>>= 2.6.16</td>
+ <td>Actual XML parser used by Arabica.</td></tr>
+ </tr>
+
+ <tr>
+ <td rowspan="1"><b>Linux</b></td>
+ <td><a href="http://www.xmlsoft.org">libxml2</a><br />required</td>
+ <td>>= 2.6.16</td>
+ <td>Actual XML parser used by Arabica.</td></tr>
+ </tr>
+
+ <tr>
+ <td rowspan="1"><b>Windows</b></td>
+ <td bgcolor="#ffffdd"><a href="http://www.microsoft.com/visualstudio/en-us">Visual&nbsp;Studio&nbsp;10</a><br />required</td>
+ <td>v10 pro works</td>
+ <td>As a student, you can get your version through MSAA.</td></tr>
+ </tr>
+</table>
+
+# Platform Notes
+
+This section will detail the preparation of the respective platforms to ultimately compile uscxml.
+
+## Mac OSX
+
+You will have to install <tt>cmake</tt> via Macports:
+
+ sudo port install cmake
+
+The rest is pre-installed or downloaded at configure-time as pre-compiled libraries.
+Just download the source and invoke CMake to create Makefiles or a Xcode project.
+
+### Console / Make
+
+ $ cd <USCXML_SRCDIR>
+ $ mkdir -p build/cli && cd build/cli
+ $ cmake ../..
+ [...]
+ -- Build files have been written to: .../build/cli
+ $ make
+
+You can test whether everything works by starting the mmi-browser with a test.scxml file:
+
+ $ ./bin/mmi-browser ../../test/samples/uscxml/test-ecmascript.scxml
+
+### Xcode
+
+ $ cd <USCXML_SRCDIR>
+ $ mkdir -p build/xcode && cd build/xcode
+ $ cmake -G Xcode ../..
+ [...]
+ -- Build files have been written to: .../build/xcode
+ $ open uscxml.xcodeproj
+
+You can of course reuse the same source directory for many build directories.
+
+## Linux
+
+Depending on your distribution, you will most likely have apt-get or yum available as package managers.
+If you do not, I'll have to assume that you are knowledgable enough to resolve build dependencies on your own.
+
+### Preparing *apt-get based* distributions
+
+This would be all distributions based on Debian, like Ubuntu, Linux Mint and the like.
+
+ # build system and compiler
+ $ sudo apt-get install git cmake cmake-curses-gui make g++
+
+ # uscxml required dependencies
+ $ sudo apt-get install libxml2-dev
+
+There may still be packages missing due to the set of dependencies among packages
+in the various distributons. Try to run cmake and resolve dependencies until you
+are satisfied.
+
+### Preparing *yum based* distributions
+
+This would be all distributions based on Redhat, e.g. Fedora.
+
+ # build system and compiler
+ $ sudo yum install git cmake cmake-gui gcc-c++
+
+ # uscxml required dependencies
+ $ sudo yum install xml2-devel
+
+### Console / Make
+
+Instructions are a literal copy of building uscxml for MacOSX on the console from above:
+
+ $ cd <USCXML_SRCDIR>
+ $ mkdir -p build/cli && cd build/cli
+ $ cmake ../..
+ [...]
+ -- Build files have been written to: .../build/cli
+ $ make
+
+You can test whether everything works by starting the mmi-browser with a test.scxml file:
+
+ $ ./bin/mmi-browser ../../test/samples/uscxml/test-ecmascript.scxml
+
+### Eclipse CDT
+
+<b>Note:</b> Eclipse does not like the project to be a subdirectory in the source.
+You have to choose your build directory with the generated project accordingly.
+
+ $ mkdir -p build/uscxml/eclipse && cd build/uscxml/eclipse
+ $ cmake -G "Eclipse CDT4 - Unix Makefiles" <USCXML_SRCDIR>
+ [...]
+ -- Build files have been written to: .../build/uscxml/eclipse
+
+Now open Eclipse CDT and import the out-of-source directory as an existing project into workspace, leaving the "Copy projects
+into workspace" checkbox unchecked. There are some more [detailed instruction](http://www.cmake.org/Wiki/Eclipse_CDT4_Generator) available
+in the cmake wiki as well.
+
+### Compiling Dependencies
+
+If the packages in your distribution are too old, you will have to compile current
+binaries. This applies especially for SWI and CMake as they *need* to be rather
+current. Have a look at the build dependencies above for minimum versions.
+
+## Windows
+
+Building from source on windows is somewhat more involved and instructions are necessarily in prose form. These instructions were
+created using Windows 7 and MS Visual Studio 2010.
+
+### Prepare compilation
+
+1. Use git to **checkout** the source from <tt>git://github.com/tklab-tud/uscxml.git</tt>
+ into any convenient directory.
+
+2. Start the **CMake-GUI** and enter the checkout directory in the "Where is the source
+ code" text field. Choose any convenient directory to build the binaries in.
+
+3. Hit "**Configure**" and choose your toolchain and compiler - I only tested with
+ Visual Studio 10. Hit "Configure" again until there are no more red items in
+ the list. If these instructions are still correct and you did as described
+ above, you should be able to "Generate" the Visual Project Solution.
+
+Now you can generate the MS Visual Studio project file <tt><USCXML_BUILDIR>/uscxml.sln</tt>.
+Just open it up to continue in your IDE.
+
+<b>Note:</b> We only tested with the MSVC compiler. You can try to compile
+with MinGW but you would have to build all the dependent libraries as well.
+
diff --git a/docs/EXTENSIONS.md b/docs/EXTENSIONS.md
new file mode 100644
index 0000000..ba77206
--- /dev/null
+++ b/docs/EXTENSIONS.md
@@ -0,0 +1,37 @@
+# Extensions
+
+## Invokers
+
+Most platform specific extensions are realized via special invokers. These are components that you can load via:
+
+ <invoke type="invokerName" invokeid="something.unique">
+ <param name="paramatername" expr="'paramatervalue'">
+ <content>
+ <invoker:someXML>
+ <invoker:here>
+ </invoker:someXML>
+ </content>
+ </invoke>
+
+### FFMPEG
+
+
+
+### Directory Monitor
+
+### 3D Scenegraph
+
+### Heartbeat
+
+### HTTP Servlet
+
+### SCXML
+
+### uMundo Publish / Subscribe
+
+
+## Elements
+
+
+
+## IO Processors \ No newline at end of file
diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp
index d14b91a..94049ea 100644
--- a/src/uscxml/Factory.cpp
+++ b/src/uscxml/Factory.cpp
@@ -28,6 +28,10 @@
# include "uscxml/plugins/invoker/modality/miles/SpatialAudio.h"
# endif
+# ifdef FFMPEG_FOUND
+# include "uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h"
+# endif
+
# ifdef V8_FOUND
# include "uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h"
# endif
@@ -42,6 +46,7 @@
# include "uscxml/plugins/element/fetch/FetchElement.h"
# include "uscxml/plugins/element/response/ResponseElement.h"
+# include "uscxml/plugins/element/postpone/PostponeElement.h"
#endif
@@ -96,6 +101,13 @@ Factory::Factory() {
}
#endif
+#ifdef FFMPEG_FOUND
+ {
+ FFMPEGInvoker* invoker = new FFMPEGInvoker();
+ registerInvoker(invoker);
+ }
+#endif
+
#ifdef OPENSCENEGRAPH_FOUND
{
OSGInvoker* invoker = new OSGInvoker();
@@ -158,6 +170,10 @@ Factory::Factory() {
ResponseElement* element = new ResponseElement();
registerExecutableContent(element);
}
+ {
+ PostponeElement* element = new PostponeElement();
+ registerExecutableContent(element);
+ }
#endif
}
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp
index 4e5b279..1e7c84e 100644
--- a/src/uscxml/Interpreter.cpp
+++ b/src/uscxml/Interpreter.cpp
@@ -137,13 +137,16 @@ void Interpreter::startPrefixMapping(const std::string& prefix, const std::strin
LOG(INFO) << "Mapped default namespace to 'scxml:'";
_xpathPrefix = "scxml:";
_nsContext.addNamespaceDeclaration(uri, "scxml");
+ _nsToPrefix[uri] = "scxml";
} else {
_xpathPrefix = prefix + ":";
_xmlNSPrefix = _xpathPrefix;
_nsContext.addNamespaceDeclaration(uri, prefix);
+ _nsToPrefix[uri] = prefix;
}
} else {
_nsContext.addNamespaceDeclaration(uri, prefix);
+ _nsToPrefix[uri] = prefix;
}
}
@@ -462,14 +465,14 @@ void Interpreter::mainEventLoop() {
if (_internalQueue.size() == 0) {
_stable = true;
} else {
- Event internalEvent = _internalQueue.front();
+ _currEvent = _internalQueue.front();
_internalQueue.pop_front();
#if VERBOSE
- std::cout << "Received internal event " << internalEvent.name << std::endl;
+ std::cout << "Received internal event " << _currEvent.name << std::endl;
#endif
if (_dataModel)
- _dataModel.setEvent(internalEvent);
- enabledTransitions = selectTransitions(internalEvent.name);
+ _dataModel.setEvent(_currEvent);
+ enabledTransitions = selectTransitions(_currEvent.name);
}
}
if (!enabledTransitions.empty()) {
@@ -509,20 +512,20 @@ void Interpreter::mainEventLoop() {
runOnMainThread(200);
}
- Event externalEvent = _externalQueue.pop();
+ _currEvent = _externalQueue.pop();
#if VERBOSE
- std::cout << "Received externalEvent event " << externalEvent.name << std::endl;
+ std::cout << "Received externalEvent event " << _currEvent.name << std::endl;
#endif
- externalEvent.type = Event::EXTERNAL; // make sure it is set to external
+ _currEvent.type = Event::EXTERNAL; // make sure it is set to external
if (!_running)
exitInterpreter();
- if (_dataModel && boost::iequals(externalEvent.name, "cancel.invoke." + _sessionId))
+ if (_dataModel && boost::iequals(_currEvent.name, "cancel.invoke." + _sessionId))
break;
if (_dataModel)
try {
- _dataModel.setEvent(externalEvent);
+ _dataModel.setEvent(_currEvent);
} catch (Event e) {
LOG(ERROR) << "Syntax error while setting external event:" << std::endl << e << std::endl;
}
@@ -537,7 +540,7 @@ void Interpreter::mainEventLoop() {
invokeId = _dataModel.evalAsString(ATTR(invokeElem, "idlocation"));
std::string autoForward = invokeElem.getAttribute("autoforward");
- if (boost::iequals(invokeId, externalEvent.invokeid)) {
+ if (boost::iequals(invokeId, _currEvent.invokeid)) {
Arabica::XPath::NodeSet<std::string> finalizes = filterChildElements(_xmlNSPrefix + "finalize", invokeElem);
for (int k = 0; k < finalizes.size(); k++) {
@@ -547,11 +550,15 @@ void Interpreter::mainEventLoop() {
}
if (boost::iequals(autoForward, "true")) {
- _invokers[invokeId].send(externalEvent);
+ try {
+ _invokers[invokeId].send(_currEvent);
+ } catch(...) {
+ LOG(ERROR) << "Exception caught while sending event to invoker " << invokeId;
+ }
}
}
}
- enabledTransitions = selectTransitions(externalEvent.name);
+ enabledTransitions = selectTransitions(_currEvent.name);
if (!enabledTransitions.empty())
microstep(enabledTransitions);
}
@@ -790,7 +797,11 @@ void Interpreter::delayedSend(void* userdata, std::string eventName) {
std::string invokeId = sendReq.target.substr(2, sendReq.target.length() - 2);
if (INSTANCE->_invokers.find(invokeId) != INSTANCE->_invokers.end()) {
tthread::lock_guard<tthread::mutex> lock(INSTANCE->_mutex);
- INSTANCE->_invokers[invokeId].send(sendReq);
+ try {
+ INSTANCE->_invokers[invokeId].send(sendReq);
+ } catch(...) {
+ LOG(ERROR) << "Exception caught while sending event to invoker " << invokeId;
+ }
} else {
LOG(ERROR) << "Can not send to invoked component '" << invokeId << "', no such invokeId" << std::endl;
}
@@ -799,7 +810,11 @@ void Interpreter::delayedSend(void* userdata, std::string eventName) {
} else {
IOProcessor ioProc = INSTANCE->getIOProcessor(sendReq.type);
if (ioProc) {
- ioProc.send(sendReq);
+ try {
+ ioProc.send(sendReq);
+ } catch(...) {
+ LOG(ERROR) << "Exception caught while sending event to ioprocessor " << sendReq.type;
+ }
}
}
assert(INSTANCE->_sendIds.find(sendReq.sendid) != INSTANCE->_sendIds.end());
@@ -905,9 +920,17 @@ void Interpreter::invoke(const Arabica::DOM::Node<std::string>& element) {
invoker.setInterpreter(this);
_invokers[invokeReq.invokeid] = invoker;
LOG(INFO) << "Added invoker " << invokeReq.type << " at " << invokeReq.invokeid;
- invoker.invoke(invokeReq);
+ try {
+ invoker.invoke(invokeReq);
+ } catch(...) {
+ LOG(ERROR) << "Exception caught while sending invoke requst to invoker " << invokeReq.invokeid;
+ }
if (_dataModel) {
- _dataModel.assign("_invokers['" + invokeReq.invokeid + "']", invoker.getDataModelVariables());
+ try {
+ _dataModel.assign("_invokers['" + invokeReq.invokeid + "']", invoker.getDataModelVariables());
+ } catch(...) {
+ LOG(ERROR) << "Exception caught while assigning datamodel variables from invoker " << invokeReq.invokeid;
+ }
}
} catch (...) {
LOG(ERROR) << "Invoker " << invokeReq.type << " threw an exception";
@@ -977,7 +1000,6 @@ Arabica::XPath::NodeSet<std::string> Interpreter::selectTransitions(const std::s
goto LOOP;
}
} else {
- LOG(ERROR) << "Transition has neither event nor eventexpr attribute";
goto LOOP;
}
@@ -1243,6 +1265,7 @@ void Interpreter::executeContent(const Arabica::DOM::Node<std::string>& content)
if (false) {
} else if (boost::iequals(TAGNAME(content), _xmlNSPrefix + "onentry") ||
boost::iequals(TAGNAME(content), _xmlNSPrefix + "onexit") ||
+ boost::iequals(TAGNAME(content), _xmlNSPrefix + "finalize") ||
boost::iequals(TAGNAME(content), _xmlNSPrefix + "transition")) {
// --- CONVENIENCE LOOP --------------------------
NodeList<std::string> executable = content.getChildNodes();
diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h
index 48e08b4..318af95 100644
--- a/src/uscxml/Interpreter.h
+++ b/src/uscxml/Interpreter.h
@@ -129,13 +129,27 @@ public:
Arabica::XPath::StandardNamespaceContext<std::string>& getNSContext() {
return _nsContext;
}
+ std::string getXMLPrefixForNS(const std::string& ns) {
+ if (_nsToPrefix.find(ns) != _nsToPrefix.end())
+ return _nsToPrefix[ns] + ":";
+ return "";
+ }
- void receive(Event& event) {
- _externalQueue.push(event);
+ void receive(const Event& event, bool toFront = false) {
+ if (toFront) {
+ _externalQueue.push_front(event);
+ } else {
+ _externalQueue.push(event);
+ }
}
- void receiveInternal(Event& event) {
+ void receiveInternal(const Event& event) {
_internalQueue.push_back(event);
}
+
+ Event getCurrentEvent() {
+ return _currEvent;
+ }
+
Arabica::XPath::NodeSet<std::string> getConfiguration() {
return _configuration;
}
@@ -202,8 +216,9 @@ protected:
Arabica::XPath::XPath<std::string> _xpath;
Arabica::XPath::StandardNamespaceContext<std::string> _nsContext;
std::string _xmlNSPrefix; // the actual prefix for elements in the xml file
- std::string _xpathPrefix; // prefix mapped for xpath, "scxml" is _xmlNSPrefix is empty but _nsURL set
+ std::string _xpathPrefix; // prefix mapped for xpath, "scxml" is _xmlNSPrefix is empty but _nsURL set
std::string _nsURL; // ough to be "http://www.w3.org/2005/07/scxml"
+ std::map<std::string, std::string> _nsToPrefix;
bool _running;
bool _done;
@@ -219,6 +234,8 @@ protected:
uscxml::concurrency::BlockingQueue<Event>* _parentQueue;
DelayedEventQueue* _sendQueue;
+ Event _currEvent;
+
HTTPServletInvoker* _httpServlet;
std::set<InterpreterMonitor*> _monitors;
diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h
index a746710..c109063 100644
--- a/src/uscxml/Message.h
+++ b/src/uscxml/Message.h
@@ -62,6 +62,9 @@ public:
Event() : type(INTERNAL) {}
Event(const Arabica::DOM::Node<std::string>& xmlString) : type(INTERNAL) {};
+ bool operator< (const Event& other) const {
+ return this < &other;
+ }
std::string name;
Type type;
diff --git a/src/uscxml/URL.cpp b/src/uscxml/URL.cpp
index 024bb0c..d410bcf 100644
--- a/src/uscxml/URL.cpp
+++ b/src/uscxml/URL.cpp
@@ -416,7 +416,6 @@ void URLFetcher::perform() {
{
tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
if (_handlesToURLs.empty()) {
- std::cout << "Waiting for work" << std::endl;
_condVar.wait(_mutex);
}
curl_multi_perform(_multiHandle, &stillRunning);
diff --git a/src/uscxml/URL.cpp.old b/src/uscxml/URL.cpp.old
deleted file mode 100644
index 20c9a18..0000000
--- a/src/uscxml/URL.cpp.old
+++ /dev/null
@@ -1,519 +0,0 @@
-#include <algorithm>
-#include <assert.h>
-#include <iostream>
-#include <fstream>
-
-#include <glog/logging.h>
-#include <boost/algorithm/string.hpp>
-
-#include <stdio.h>
-#include <string.h>
-#ifndef WIN32
-#include <sys/time.h>
-#endif
-#include <stdlib.h>
-#include <errno.h>
-
-#include "uscxml/Common.h"
-#include "URL.h"
-
-#include "uscxml/config.h"
-
-#include <stdio.h> /* defines FILENAME_MAX */
-#ifdef _WIN32
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <io.h>
-#include <direct.h>
-#define getcwd _getcwd
-#else
-#include <unistd.h>
-#endif
-
-#include <cstdlib> // mkstemp
-#ifdef HAS_UNISTD_H
-#include <unistd.h> // mkstemp legacy
-#endif
-
-namespace uscxml {
-
-URLImpl::~URLImpl() {
- if (_localFile.length() > 0)
- remove(_localFile.c_str());
-}
-
-const bool URLImpl::toAbsoluteCwd() {
- char currPath[FILENAME_MAX];
- if (!getcwd(currPath, sizeof(currPath))) {
- return false;
- }
- currPath[sizeof(currPath) - 1] = '\0'; /* not really required */
- return toAbsolute(std::string("file://" + std::string(currPath) + "/"));
-}
-
-std::string URLImpl::getLocalFilename(const std::string& suffix) {
- if (_localFile.length() > 0)
- return _localFile;
-
- if (_uri.scheme().compare("file") == 0)
- return _uri.path();
-
- // try hard to find a temporary directory
- const char* tmpDir = NULL;
- if (tmpDir == NULL)
- tmpDir = getenv("TMPDIR");
- if (tmpDir == NULL)
- tmpDir = getenv("TMP");
- if (tmpDir == NULL)
- tmpDir = getenv("TEMP");
- if (tmpDir == NULL)
- tmpDir = getenv("USERPROFILE");
- if (tmpDir == NULL)
- tmpDir = "/tmp";
-
- char* tmpl = (char*)malloc(strlen(tmpDir) + 11 + suffix.length());
- char* writePtr = tmpl;
- memcpy(writePtr, tmpDir, strlen(tmpDir));
- writePtr += strlen(tmpDir);
- memcpy(writePtr, "scxmlXXXXXX", 11);
- writePtr += 11;
- memcpy(writePtr, suffix.c_str(), suffix.length());
- writePtr += suffix.length();
- tmpl[writePtr - tmpl] = 0;
-
-#ifdef _WIN32
- _mktemp_s(tmpl, strlen(tmpl) + 1);
- int fd = _open(tmpl, _O_CREAT, _S_IREAD | _S_IWRITE);
-#else
- int fd = mkstemps(tmpl, suffix.length());
-#endif
- if (fd < 0) {
- LOG(ERROR) << "mkstemp " << tmpl << ": " << strerror(errno) << std::endl;
- return "";
- }
-#ifdef WIN32
- _close(fd);
-#else
- close(fd);
-#endif
- return std::string(tmpl);
-}
-
-boost::shared_ptr<URLImpl> URLImpl::toLocalFile(const std::string& content, const std::string& suffix) {
- boost::shared_ptr<URLImpl> urlImpl = boost::shared_ptr<URLImpl>(new URLImpl());
- urlImpl->_localFile = urlImpl->getLocalFilename(suffix);
- urlImpl->_uri = std::string("file://") + urlImpl->_localFile;
-
- std::ofstream file(urlImpl->_localFile.c_str(), std::ios_base::out);
- if(file.is_open()) {
- file << content;
- file.close();
- } else {
- return boost::shared_ptr<URLImpl>();
- }
-
- return urlImpl;
-}
-
-const bool URLImpl::toAbsolute(const std::string& baseUrl) {
- if (_uri.is_absolute())
- return true;
- _uri = Arabica::io::URI(baseUrl, _uri.as_string());
- if (!_uri.is_absolute())
- return false;
- return true;
-}
-
-const std::string URLImpl::asLocalFile(const std::string& suffix, bool reload) {
- // this is already a local file
- if (_uri.scheme().compare("file") == 0)
- return _uri.path();
-
- if (_localFile.length() > 0 && !reload)
- return _localFile;
-
- if (_localFile.length() > 0)
- remove(_localFile.c_str());
-
- _localFile = getLocalFilename(suffix);
-
- std::ofstream file(_localFile.c_str(), std::ios_base::out);
- if(file.is_open()) {
- file << URL(this->shared_from_this());
- file.close();
- } else {
- _localFile = "";
- }
-
- return _localFile;
-}
-
-std::ostream & operator<<(std::ostream & stream, const URL& url) {
-
- std::string urlString = url.asString();
- std::string fileURL = "file://";
-
- // strip file:// to support relative filenames
- if(urlString.substr(0, fileURL.size()) == fileURL) {
- urlString = urlString.substr(fileURL.size());
-#ifdef _WIN32
- urlString = urlString.substr(0,1) + ":" + urlString.substr(1);
-// std::replace( urlString.begin(), urlString.end(), '/', '\\');
-#endif
- }
-// LOG(ERROR) << "Trying to open " << urlString;
- URL_FILE *handle = url_fopen(urlString.c_str(), "r");
-
- if(!handle) {
- LOG(ERROR) << "Cannot open URL " << url.asString();
- return stream;
- }
-
- int nread;
- char buffer[256];
-
- do {
- nread = url_fread(buffer, 1,sizeof(buffer), handle);
- stream.write(buffer, nread);
- } while(nread);
-
- url_fclose(handle);
- return stream;
-}
-
-/* we use a global one for convenience */
-CURLM *multi_handle;
-
-/* curl calls this routine to get more data */
-static size_t write_callback(char *buffer,
- size_t size,
- size_t nitems,
- void *userp) {
- char *newbuff;
- size_t rembuff;
-
- URL_FILE *url = (URL_FILE *)userp;
- size *= nitems;
-
- rembuff=url->buffer_len - url->buffer_pos; /* remaining space in buffer */
-
- if(size > rembuff) {
- /* not enough space in buffer */
- newbuff=(char*)realloc(url->buffer,url->buffer_len + (size - rembuff));
- if(newbuff==NULL) {
- fprintf(stderr,"callback buffer grow failed\n");
- size=rembuff;
- } else {
- /* realloc suceeded increase buffer size*/
- url->buffer_len+=size - rembuff;
- url->buffer=newbuff;
- }
- }
-
- memcpy(&url->buffer[url->buffer_pos], buffer, size);
- url->buffer_pos += size;
-
- return size;
-}
-
-/* use to attempt to fill the read buffer up to requested number of bytes */
-static int fill_buffer(URL_FILE *file, size_t want) {
- fd_set fdread;
- fd_set fdwrite;
- fd_set fdexcep;
- struct timeval timeout;
- int rc;
-
- /* only attempt to fill buffer if transactions still running and buffer
- * doesnt exceed required size already
- */
- if((!file->still_running) || (file->buffer_pos > want))
- return 0;
-
- /* attempt to fill buffer */
- do {
- int maxfd = -1;
- long curl_timeo = -1;
-
- FD_ZERO(&fdread);
- FD_ZERO(&fdwrite);
- FD_ZERO(&fdexcep);
-
- /* set a suitable timeout to fail on */
- timeout.tv_sec = 60; /* 1 minute */
- timeout.tv_usec = 0;
-
- curl_multi_timeout(multi_handle, &curl_timeo);
- if(curl_timeo >= 0) {
- timeout.tv_sec = curl_timeo / 1000;
- if(timeout.tv_sec > 1)
- timeout.tv_sec = 1;
- else
- timeout.tv_usec = (curl_timeo % 1000) * 1000;
- }
-
- /* get file descriptors from the transfers */
- curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
-
- /* In a real-world program you OF COURSE check the return code of the
- function calls. On success, the value of maxfd is guaranteed to be
- greater or equal than -1. We call select(maxfd + 1, ...), specially
- in case of (maxfd == -1), we call select(0, ...), which is basically
- equal to sleep. */
-
- rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
-
- switch(rc) {
- case -1:
- /* select error */
- break;
-
- case 0:
- default:
- /* timeout or readable/writable sockets */
- curl_multi_perform(multi_handle, &file->still_running);
- break;
- }
- } while(file->still_running && (file->buffer_pos < want));
- return 1;
-}
-
-/* use to remove want bytes from the front of a files buffer */
-static int use_buffer(URL_FILE *file,int want) {
- /* sort out buffer */
- if((file->buffer_pos - want) <=0) {
- /* ditch buffer - write will recreate */
- if(file->buffer)
- free(file->buffer);
-
- file->buffer=NULL;
- file->buffer_pos=0;
- file->buffer_len=0;
- } else {
- /* move rest down make it available for later */
- memmove(file->buffer,
- &file->buffer[want],
- (file->buffer_pos - want));
-
- file->buffer_pos -= want;
- }
- return 0;
-}
-
-URL_FILE *url_fopen(const char *url,const char *operation) {
- /* this code could check for URLs or types in the 'url' and
- basicly use the real fopen() for standard files */
-
- URL_FILE *file;
- (void)operation;
-
- file = (URL_FILE*)malloc(sizeof(URL_FILE));
- if(!file)
- return NULL;
-
- memset(file, 0, sizeof(URL_FILE));
-
- if((file->handle.file=fopen(url,operation)))
- file->type = CFTYPE_FILE; /* marked as URL */
-
- else {
- file->type = CFTYPE_CURL; /* marked as URL */
- file->handle.curl = curl_easy_init();
-
- curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
- curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file);
- curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L);
- curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);
-
- if(!multi_handle)
- multi_handle = curl_multi_init();
-
- curl_multi_add_handle(multi_handle, file->handle.curl);
-
- /* lets start the fetch */
- curl_multi_perform(multi_handle, &file->still_running);
-
- if((file->buffer_pos == 0) && (!file->still_running)) {
- /* if still_running is 0 now, we should return NULL */
-
- /* make sure the easy handle is not in the multi handle anymore */
- curl_multi_remove_handle(multi_handle, file->handle.curl);
-
- /* cleanup */
- curl_easy_cleanup(file->handle.curl);
-
- free(file);
-
- file = NULL;
- }
- }
- return file;
-}
-
-int url_fclose(URL_FILE *file) {
- int ret=0;/* default is good return */
-
- switch(file->type) {
- case CFTYPE_FILE:
- ret=fclose(file->handle.file); /* passthrough */
- break;
-
- case CFTYPE_CURL:
- /* make sure the easy handle is not in the multi handle anymore */
- curl_multi_remove_handle(multi_handle, file->handle.curl);
-
- /* cleanup */
- curl_easy_cleanup(file->handle.curl);
- break;
-
- default: /* unknown or supported type - oh dear */
- ret=EOF;
- errno=EBADF;
- break;
- }
-
- if(file->buffer)
- free(file->buffer);/* free any allocated buffer space */
-
- free(file);
-
- return ret;
-}
-
-int url_feof(URL_FILE *file) {
- int ret=0;
-
- switch(file->type) {
- case CFTYPE_FILE:
- ret=feof(file->handle.file);
- break;
-
- case CFTYPE_CURL:
- if((file->buffer_pos == 0) && (!file->still_running))
- ret = 1;
- break;
-
- default: /* unknown or supported type - oh dear */
- ret=-1;
- errno=EBADF;
- break;
- }
- return ret;
-}
-
-size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file) {
- size_t want;
-
- switch(file->type) {
- case CFTYPE_FILE:
- want=fread(ptr,size,nmemb,file->handle.file);
- break;
-
- case CFTYPE_CURL:
- want = nmemb * size;
-
- fill_buffer(file,want);
-
- /* check if theres data in the buffer - if not fill_buffer()
- * either errored or EOF */
- if(!file->buffer_pos)
- return 0;
-
- /* ensure only available data is considered */
- if(file->buffer_pos < want)
- want = file->buffer_pos;
-
- /* xfer data to caller */
- memcpy(ptr, file->buffer, want);
-
- use_buffer(file,want);
-
- want = want / size; /* number of items */
- break;
-
- default: /* unknown or supported type - oh dear */
- want=0;
- errno=EBADF;
- break;
-
- }
- return want;
-}
-
-char *url_fgets(char *ptr, size_t size, URL_FILE *file) {
- size_t want = size - 1;/* always need to leave room for zero termination */
- size_t loop;
-
- switch(file->type) {
- case CFTYPE_FILE:
- ptr = fgets(ptr,size,file->handle.file);
- break;
-
- case CFTYPE_CURL:
- fill_buffer(file,want);
-
- /* check if theres data in the buffer - if not fill either errored or
- * EOF */
- if(!file->buffer_pos)
- return NULL;
-
- /* ensure only available data is considered */
- if(file->buffer_pos < want)
- want = file->buffer_pos;
-
- /*buffer contains data */
- /* look for newline or eof */
- for(loop=0; loop < want; loop++) {
- if(file->buffer[loop] == '\n') {
- want=loop+1;/* include newline */
- break;
- }
- }
-
- /* xfer data to caller */
- memcpy(ptr, file->buffer, want);
- ptr[want]=0;/* allways null terminate */
-
- use_buffer(file,want);
-
- break;
-
- default: /* unknown or supported type - oh dear */
- ptr=NULL;
- errno=EBADF;
- break;
- }
-
- return ptr;/*success */
-}
-
-void url_rewind(URL_FILE *file) {
- switch(file->type) {
- case CFTYPE_FILE:
- rewind(file->handle.file); /* passthrough */
- break;
-
- case CFTYPE_CURL:
- /* halt transaction */
- curl_multi_remove_handle(multi_handle, file->handle.curl);
-
- /* restart */
- curl_multi_add_handle(multi_handle, file->handle.curl);
-
- /* ditch buffer - write will recreate - resets stream pos*/
- if(file->buffer)
- free(file->buffer);
-
- file->buffer=NULL;
- file->buffer_pos=0;
- file->buffer_len=0;
-
- break;
-
- default: /* unknown or supported type - oh dear */
- break;
- }
-}
-
-} \ No newline at end of file
diff --git a/src/uscxml/URL.h.old b/src/uscxml/URL.h.old
deleted file mode 100644
index 9ff24c5..0000000
--- a/src/uscxml/URL.h.old
+++ /dev/null
@@ -1,154 +0,0 @@
-#ifndef URL_H_27HPRH76
-#define URL_H_27HPRH76
-
-#include <string>
-#include <sstream>
-#include <curl/curl.h>
-
-// use arabica URL parser
-#include <io/uri.hpp>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/enable_shared_from_this.hpp>
-
-namespace uscxml {
-
-class URLImpl : public boost::enable_shared_from_this<URLImpl> {
-public:
- URLImpl() {}
- URLImpl(const std::string uri) : _uri(uri) {}
- virtual ~URLImpl();
- const bool toAbsoluteCwd();
- const bool toAbsolute(const std::string& baseUrl);
- const std::string asLocalFile(const std::string& suffix, bool reload = false);
-
- static boost::shared_ptr<URLImpl> toLocalFile(const std::string& content, const std::string& suffix);
-
- const bool isAbsolute() const {
- return _uri.is_absolute();
- }
- const std::string scheme() const {
- return _uri.scheme();
- }
- const std::string host() const {
- return _uri.host();
- }
- const std::string port() const {
- return _uri.port();
- }
- const std::string path() const {
- return _uri.path();
- }
- const std::string asString() const {
- return _uri.as_string();
- }
-
-private:
- std::string getLocalFilename(const std::string& suffix);
-
- Arabica::io::URI _uri;
- std::string _localFile;
-};
-
-class URL {
-public:
- URL() : _impl() {}
- URL(const std::string uri) : _impl(new URLImpl(uri)) {}
- URL(boost::shared_ptr<URLImpl> const impl) : _impl(impl) { }
- URL(const URL& other) : _impl(other._impl) { }
- virtual ~URL() {};
-
- static URL toLocalFile(const std::string& content, const std::string& suffix) {
- boost::shared_ptr<URLImpl> impl = URLImpl::toLocalFile(content, suffix);
- return URL(impl);
- }
-
- operator bool() const {
- return _impl;
- }
- bool operator< (const URL& other) const {
- return _impl < other._impl;
- }
- bool operator==(const URL& other) const {
- return _impl == other._impl;
- }
- bool operator!=(const URL& other) const {
- return _impl != other._impl;
- }
- URL& operator= (const URL& other) {
- _impl = other._impl;
- return *this;
- }
-
- const bool toAbsoluteCwd() {
- return _impl->toAbsoluteCwd();
- }
- const bool toAbsolute(const std::string& baseUrl) {
- return _impl->toAbsolute(baseUrl);
- }
- const bool toAbsolute(const URL& baseUrl) {
- return _impl->toAbsolute(baseUrl.asString());
- }
- const std::string asLocalFile(const std::string& suffix, bool reload = false) {
- return _impl->asLocalFile(suffix, reload);
- }
-
- const bool isAbsolute() const {
- return _impl->isAbsolute();
- }
- const std::string scheme() const {
- return _impl->scheme();
- }
- const std::string host() const {
- return _impl->host();
- }
- const std::string port() const {
- return _impl->port();
- }
- const std::string path() const {
- return _impl->path();
- }
- const std::string asString() const {
- return _impl->asString();
- }
-
- friend std::ostream & operator<<(std::ostream &stream, const URL& p);
-
-protected:
- boost::shared_ptr<URLImpl> _impl;
-};
-
-enum fcurl_type_e {
- CFTYPE_NONE=0,
- CFTYPE_FILE=1,
- CFTYPE_CURL=2
-};
-
-struct fcurl_data {
- enum fcurl_type_e type; /* type of handle */
- union {
- CURL *curl;
- FILE *file;
- } handle; /* handle */
-
- char *buffer; /* buffer to store cached data*/
- size_t buffer_len; /* currently allocated buffers length */
- size_t buffer_pos; /* end of data in buffer*/
- int still_running; /* Is background url fetch still in progress */
-};
-
-typedef struct fcurl_data URL_FILE;
-
-URL_FILE *url_fopen(const char *url,const char *operation);
-int url_fclose(URL_FILE *file);
-int url_feof(URL_FILE *file);
-size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file);
-char * url_fgets(char *ptr, size_t size, URL_FILE *file);
-void url_rewind(URL_FILE *file);
-
-std::ostream & operator<<(std::ostream &stream, const URL& url);
-
-}
-
-
-#endif /* end of include guard: URL_H_27HPRH76 */
diff --git a/src/uscxml/concurrency/BlockingQueue.h b/src/uscxml/concurrency/BlockingQueue.h
index f318ccf..0f4c965 100644
--- a/src/uscxml/concurrency/BlockingQueue.h
+++ b/src/uscxml/concurrency/BlockingQueue.h
@@ -14,12 +14,18 @@ public:
virtual ~BlockingQueue() {
}
- virtual void push(T& elem) {
+ virtual void push(const T& elem) {
tthread::lock_guard<tthread::mutex> lock(_mutex);
_queue.push_back(elem);
_cond.notify_all();
}
+ virtual void push_front(const T& elem) {
+ tthread::lock_guard<tthread::mutex> lock(_mutex);
+ _queue.push_front(elem);
+ _cond.notify_all();
+ }
+
virtual T pop() {
tthread::lock_guard<tthread::mutex> lock(_mutex);
while (_queue.empty()) {
diff --git a/src/uscxml/concurrency/tinythread.cpp b/src/uscxml/concurrency/tinythread.cpp
index 66e73c1..6167545 100644
--- a/src/uscxml/concurrency/tinythread.cpp
+++ b/src/uscxml/concurrency/tinythread.cpp
@@ -295,4 +295,24 @@ thread::id this_thread::get_id() {
#endif
}
+namespace chrono {
+namespace system_clock {
+uint64_t now() {
+ uint64_t time = 0;
+#ifdef _WIN32
+ FILETIME tv;
+ GetSystemTimeAsFileTime(&tv);
+ time = (((uint64_t) tv.dwHighDateTime) << 32) + tv.dwLowDateTime;
+ time /= 10000;
+#else
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ time += tv.tv_sec * 1000;
+ time += tv.tv_usec / 1000;
+#endif
+ return time;
+}
+}
+}
+
}
diff --git a/src/uscxml/concurrency/tinythread.h b/src/uscxml/concurrency/tinythread.h
index d824067..9dd2fcb 100644
--- a/src/uscxml/concurrency/tinythread.h
+++ b/src/uscxml/concurrency/tinythread.h
@@ -646,6 +646,11 @@ typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration w
typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds.
typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes.
typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours.
+
+namespace system_clock {
+uint64_t now();
+}
+
}
/// The namespace @c this_thread provides methods for dealing with the
diff --git a/src/uscxml/plugins/element/postpone/PostponeElement.cpp b/src/uscxml/plugins/element/postpone/PostponeElement.cpp
new file mode 100644
index 0000000..644cb1d
--- /dev/null
+++ b/src/uscxml/plugins/element/postpone/PostponeElement.cpp
@@ -0,0 +1,97 @@
+#include "PostponeElement.h"
+#include "uscxml/plugins/invoker/http/HTTPServletInvoker.h"
+#include <glog/logging.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include <Pluma/Connector.hpp>
+#endif
+
+namespace uscxml {
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_CONNECTOR
+bool connect(pluma::Host& host) {
+ host.add( new PostponeElementProvider() );
+ return true;
+}
+#endif
+
+boost::shared_ptr<ExecutableContentImpl> PostponeElement::create(Interpreter* interpreter) {
+ boost::shared_ptr<PostponeElement> invoker = boost::shared_ptr<PostponeElement>(new PostponeElement());
+ invoker->_interpreter = interpreter;
+ return invoker;
+}
+
+void PostponeElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
+ if (!_interpreter->getDataModel()) {
+ LOG(ERROR) << "Postpone element requires a datamodel";
+ return;
+ }
+
+ // under which condition will we postpone the current event?
+ if (HAS_ATTR(node, "cond")) {
+ std::string cond = ATTR(node, "cond");
+ try {
+ if (!_interpreter->getDataModel().evalAsBool(cond))
+ return;
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in cond attribute of postpone element:" << std::endl << e << std::endl;
+ return;
+ }
+ }
+
+ // when will we refire the event?
+ if (!HAS_ATTR(node, "until")) {
+ LOG(ERROR) << "Postpone element requires until attribute ";
+ return;
+ }
+ std::string until = ATTR(node, "until");
+
+ Event currEvent = _interpreter->getCurrentEvent();
+ Resubmitter::postpone(currEvent, until, _interpreter);
+}
+
+void PostponeElement::exitElement(const Arabica::DOM::Node<std::string>& node) {
+}
+
+void PostponeElement::Resubmitter::postpone(const Event& event, std::string until, Interpreter* interpreter) {
+ Resubmitter* resubmitter = getInstance(interpreter);
+ resubmitter->_postponedEvents.push_back(std::make_pair(until, event));
+}
+
+void PostponeElement::Resubmitter::onStableConfiguration(Interpreter* interpreter) {
+ std::list<std::pair<std::string, Event> >::iterator eventIter = _postponedEvents.begin();
+ while(eventIter != _postponedEvents.end()) {
+ try {
+ if (interpreter->getDataModel().evalAsBool(eventIter->first)) {
+ interpreter->receive(eventIter->second, true);
+ _postponedEvents.erase(eventIter);
+ break;
+ }
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error in until attribute of postpone element:" << std::endl << e << std::endl;
+ _postponedEvents.erase(eventIter++);
+ continue;
+ }
+ eventIter++;
+ }
+}
+
+void PostponeElement::Resubmitter::afterCompletion(Interpreter* interpreter) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(PostponeElement::Resubmitter::_accessLock);
+ _instances.erase(interpreter);
+ delete this; // committing suicide is ok if we are careful
+}
+
+std::map<Interpreter*, PostponeElement::Resubmitter*> PostponeElement::Resubmitter::_instances;
+tthread::recursive_mutex PostponeElement::Resubmitter::_accessLock;
+
+PostponeElement::Resubmitter* PostponeElement::Resubmitter::getInstance(Interpreter* interpreter) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(PostponeElement::Resubmitter::_accessLock);
+ if (_instances.find(interpreter) == _instances.end()) {
+ _instances[interpreter] = new Resubmitter(interpreter);
+ }
+ return _instances[interpreter];
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/plugins/element/postpone/PostponeElement.h b/src/uscxml/plugins/element/postpone/PostponeElement.h
new file mode 100644
index 0000000..03aafde
--- /dev/null
+++ b/src/uscxml/plugins/element/postpone/PostponeElement.h
@@ -0,0 +1,62 @@
+#ifndef POSTPONEELEMENT_H_WN8EIYYI
+#define POSTPONEELEMENT_H_WN8EIYYI
+
+#include <uscxml/Interpreter.h>
+#include <list>
+
+#ifdef BUILD_AS_PLUGINS
+#include "uscxml/plugins/Plugins.h"
+#endif
+
+namespace uscxml {
+
+class PostponeElement : public ExecutableContentImpl {
+public:
+ PostponeElement() {}
+ virtual ~PostponeElement() {}
+ boost::shared_ptr<ExecutableContentImpl> create(Interpreter* interpreter);
+
+ std::string getLocalName() {
+ return "postpone";
+ }
+
+ std::string getNamespace() {
+ return "http://www.w3.org/2005/07/scxml";
+ }
+
+ bool processChildren() {
+ return false;
+ }
+
+ void enterElement(const Arabica::DOM::Node<std::string>& node);
+ void exitElement(const Arabica::DOM::Node<std::string>& node);
+
+protected:
+ // once per interpreter
+ class Resubmitter : public InterpreterMonitor {
+ public:
+ Resubmitter(Interpreter* interpreter) {
+ interpreter->addMonitor(this);
+ }
+
+ static Resubmitter* getInstance(Interpreter* interpreter);
+ static void postpone(const Event& event, std::string until, Interpreter* interpreter);
+
+ // InterpreterMonitor
+ void onStableConfiguration(Interpreter* interpreter);
+ void afterCompletion(Interpreter* interpreter);
+
+ std::list<std::pair<std::string, Event> > _postponedEvents;
+ static std::map<Interpreter*, Resubmitter*> _instances;
+ static tthread::recursive_mutex _accessLock;
+
+ };
+};
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_INHERIT_PROVIDER(PostponeElement, Element);
+#endif
+
+}
+
+#endif /* end of include guard: POSTPONEELEMENT_H_WN8EIYYI */
diff --git a/src/uscxml/plugins/element/response/ResponseElement.cpp b/src/uscxml/plugins/element/response/ResponseElement.cpp
index 814f726..7f1a479 100644
--- a/src/uscxml/plugins/element/response/ResponseElement.cpp
+++ b/src/uscxml/plugins/element/response/ResponseElement.cpp
@@ -23,6 +23,7 @@ boost::shared_ptr<ExecutableContentImpl> ResponseElement::create(Interpreter* in
}
void ResponseElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
+ // try to get the request id
if (!HAS_ATTR(node, "request") && !HAS_ATTR(node, "requestexpr")) {
LOG(ERROR) << "Response element requires request or requestexpr";
return;
@@ -31,12 +32,9 @@ void ResponseElement::enterElement(const Arabica::DOM::Node<std::string>& node)
LOG(ERROR) << "Response element with requestexpr requires datamodel";
return;
}
- if (HAS_ATTR(node, "close")) {
-
- }
-
std::string requestId = (HAS_ATTR(node, "request") ? ATTR(node, "request") : _interpreter->getDataModel().evalAsString(ATTR(node, "requestexpr")));
+ // try to get the request object
HTTPServletInvoker* servlet = _interpreter->getHTTPServlet();
tthread::lock_guard<tthread::recursive_mutex> lock(servlet->getMutex());
@@ -44,19 +42,39 @@ void ResponseElement::enterElement(const Arabica::DOM::Node<std::string>& node)
LOG(ERROR) << "No matching HTTP request for response element";
return;
}
+ HTTPServer::Request httpReq = servlet->getRequests()[requestId];
+ HTTPServer::Reply httpReply(httpReq);
+ // get the status or default to 200
std::string statusStr = (HAS_ATTR(node, "status") ? ATTR(node, "status") : "200");
if (!isNumeric(statusStr.c_str(), 10)) {
LOG(ERROR) << "Response element with non-numeric status " << statusStr;
return;
}
- int status = strTo<int>(statusStr);
-
- HTTPServer::Request httpReq = servlet->getRequests()[requestId];
-
- HTTPServer::Reply httpReply(httpReq);
- httpReply.status = status;
+ httpReply.status = strTo<int>(statusStr);;
+
+ // extract the content
+ Arabica::XPath::NodeSet<std::string> contents = Interpreter::filterChildElements(_interpreter->getXMLPrefixForNS(getNamespace()) + "content", node);
+ if (contents.size() > 0) {
+ if (HAS_ATTR(contents[0], "expr")) {
+ if (_interpreter->getDataModel()) {
+ try {
+ std::string contentValue = _interpreter->getDataModel().evalAsString(ATTR(contents[0], "expr"));
+ httpReply.content = contentValue;
+ } catch (Event e) {
+ LOG(ERROR) << "Syntax error with expr in content child of response element:" << std::endl << e << std::endl;
+ }
+ } else {
+ LOG(ERROR) << "content element has expr attribute but no datamodel is specified.";
+ }
+ } else if (contents[0].hasChildNodes()) {
+ httpReply.content = contents[0].getFirstChild().getNodeValue();
+ } else {
+ LOG(ERROR) << "content element does not specify any content.";
+ }
+ }
+ // send the reply
HTTPServer::reply(httpReply);
servlet->getRequests().erase(requestId);
}
diff --git a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp
new file mode 100644
index 0000000..05af363
--- /dev/null
+++ b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp
@@ -0,0 +1,48 @@
+#include "FFMPEGInvoker.h"
+#include <glog/logging.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include <Pluma/Connector.hpp>
+#endif
+
+namespace uscxml {
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_CONNECTOR
+bool connect(pluma::Host& host) {
+ host.add( new FFMPEGInvokerProvider() );
+ return true;
+}
+#endif
+
+FFMPEGInvoker::FFMPEGInvoker() {
+}
+
+FFMPEGInvoker::~FFMPEGInvoker() {
+};
+
+boost::shared_ptr<IOProcessorImpl> FFMPEGInvoker::create(Interpreter* interpreter) {
+ boost::shared_ptr<FFMPEGInvoker> invoker = boost::shared_ptr<FFMPEGInvoker>(new FFMPEGInvoker());
+ invoker->_interpreter = interpreter;
+ return invoker;
+}
+
+Data FFMPEGInvoker::getDataModelVariables() {
+ Data data;
+ return data;
+}
+
+void FFMPEGInvoker::send(const SendRequest& req) {
+}
+
+void FFMPEGInvoker::cancel(const std::string sendId) {
+}
+
+void FFMPEGInvoker::invoke(const InvokeRequest& req) {
+// AVIOContext* avCtx = avio_alloc_context();
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h
new file mode 100644
index 0000000..9b1b0ca
--- /dev/null
+++ b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h
@@ -0,0 +1,40 @@
+#ifndef FFMPEGINVOKER_H_VQD1V1C2
+#define FFMPEGINVOKER_H_VQD1V1C2
+
+#include <uscxml/Interpreter.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include "uscxml/plugins/Plugins.h"
+#endif
+
+namespace uscxml {
+
+class FFMPEGInvoker : public InvokerImpl {
+public:
+ FFMPEGInvoker();
+ virtual ~FFMPEGInvoker();
+ virtual boost::shared_ptr<IOProcessorImpl> create(Interpreter* interpreter);
+
+ virtual std::set<std::string> getNames() {
+ std::set<std::string> names;
+ names.insert("ffmpeg");
+ names.insert("http://uscxml.tk.informatik.tu-darmstadt.de/#ffmpeg");
+ return names;
+ }
+
+ virtual Data getDataModelVariables();
+ virtual void send(const SendRequest& req);
+ virtual void cancel(const std::string sendId);
+ virtual void invoke(const InvokeRequest& req);
+
+protected:
+};
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_INHERIT_PROVIDER(FFMPEGInvoker, Invoker);
+#endif
+
+}
+
+
+#endif /* end of include guard: FFMPEGINVOKER_H_VQD1V1C2 */
diff --git a/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp b/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp
index 9486de3..e34517d 100644
--- a/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp
+++ b/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp
@@ -1,6 +1,8 @@
#include "DirMonInvoker.h"
#include <glog/logging.h>
+#include "uscxml/config.h"
+
#ifdef BUILD_AS_PLUGINS
#include <Pluma/Connector.hpp>
#endif
@@ -111,13 +113,38 @@ void DirMonInvoker::handleFileAction(FW::WatchID watchid, const FW::String& dir,
case FW::Actions::Modified:
event.name = "file.modified";
break;
-
default:
break;
}
- event.data.compound["file"].compound["name"] = Data(filename, Data::VERBATIM);
- event.data.compound["file"].compound["dir"] = Data(dir, Data::VERBATIM);
+ std::string basename;
+ size_t lastSep;
+ if ((lastSep = filename.find_last_of(PATH_SEPERATOR)) != std::string::npos) {
+ lastSep++;
+ basename = filename.substr(lastSep, filename.length() - lastSep);
+ } else {
+ basename = filename;
+ }
+
+ std::string extension;
+ size_t lastDot;
+ if ((lastDot = basename.find_last_of(".")) != std::string::npos) {
+ lastDot++;
+ extension = basename.substr(lastDot, basename.length() - lastDot);
+ }
+
+ std::string relPath;
+ if (boost::algorithm::starts_with(filename, dir)) {
+ relPath = filename.substr(dir.length());
+ } else {
+ relPath = filename;
+ }
+
+ event.data.compound["file"].compound["name"] = Data(basename, Data::VERBATIM);
+ event.data.compound["file"].compound["path"] = Data(filename, Data::VERBATIM);
+ event.data.compound["file"].compound["relPath"] = Data(relPath, Data::VERBATIM);
+ event.data.compound["file"].compound["dir"] = Data(dir, Data::VERBATIM);
+ event.data.compound["file"].compound["extension"] = Data(extension, Data::VERBATIM);
event.data.compound["file"].compound["mtime"] = toStr(fileStat.st_mtime);
event.data.compound["file"].compound["ctime"] = toStr(fileStat.st_ctime);
diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.cpp b/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.cpp
index 49c9ccb..726cd08 100644
--- a/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.cpp
+++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.cpp
@@ -1,5 +1,12 @@
#include "OSGConverter.h"
#include <glog/logging.h>
+#include "uscxml/config.h"
+
+#include <osg/MatrixTransform>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+#include <osgDB/Registry>
+#include <osgGA/TrackballManipulator>
#ifdef BUILD_AS_PLUGINS
#include <Pluma/Connector.hpp>
@@ -15,10 +22,15 @@ bool connect(pluma::Host& host) {
}
#endif
-OSGConverter::OSGConverter() {
+OSGConverter::OSGConverter() : _isRunning(false) {
}
OSGConverter::~OSGConverter() {
+ _isRunning = false;
+ std::set<tthread::thread*>::iterator threadIter = _threads.begin();
+ while(threadIter != _threads.end()) {
+ (*threadIter)->join();
+ }
};
boost::shared_ptr<IOProcessorImpl> OSGConverter::create(Interpreter* interpreter) {
@@ -33,16 +45,294 @@ Data OSGConverter::getDataModelVariables() {
}
void OSGConverter::send(const SendRequest& req) {
- std::cout << req << std::endl;
- Event event;
- event.name = "error";
- returnEvent(event);
+
+ /**
+ * we have to resolve all datamodel dependent strings first as
+ * we cannot access the datamodel from within another thread without locking
+ */
+
+ // make a copy
+ SendRequest actualReq(req);
+
+ if (actualReq.params.find("source") == actualReq.params.end()) {
+ // no explicit source
+ if (actualReq.params.find("sourceexpr") != actualReq.params.end() && _interpreter->getDataModel()) {
+ actualReq.params.insert(std::make_pair("source", _interpreter->getDataModel().evalAsString(actualReq.params.find("sourceexpr")->second)));
+ } else {
+ LOG(ERROR) << "SendRequests for osginvoker missing source or sourceExpr and datamodel";
+ return;
+ }
+ }
+
+ if (actualReq.params.find("dest") == actualReq.params.end()) {
+ // no explicit destination
+ if (actualReq.params.find("destexpr") != actualReq.params.end() && _interpreter->getDataModel()) {
+ actualReq.params.insert(std::make_pair("dest", _interpreter->getDataModel().evalAsString(actualReq.params.find("destexpr")->second)));
+ } else {
+ LOG(ERROR) << "SendRequests for osginvoker missing dest or destExpr and datamodel";
+ return;
+ }
+ }
+
+ if (actualReq.params.find("format") == actualReq.params.end()) {
+ // no explicit format
+ if (actualReq.params.find("formatexpr") != actualReq.params.end() && _interpreter->getDataModel()) {
+ actualReq.params.insert(std::make_pair("format", _interpreter->getDataModel().evalAsString(actualReq.params.find("formatexpr")->second)));
+ } else {
+ std::string format;
+ size_t lastDot;
+ std::string dest = req.params.find("dest")->second;
+ if ((lastDot = dest.find_last_of(".")) != std::string::npos) {
+ lastDot++;
+ format = dest.substr(lastDot, dest.length() - lastDot);
+ }
+ if (format.length() == 0 || format.find_last_of(PATH_SEPERATOR) != std::string::npos) {
+ // empty format or pathseperator in format
+ format = "png";
+ }
+ actualReq.params.insert(std::make_pair("format", format));
+ }
+ }
+
+ if (actualReq.params.find("height") == actualReq.params.end()) {
+ // no explicit height
+ if (actualReq.params.find("heightexpr") != actualReq.params.end() && _interpreter->getDataModel()) {
+ actualReq.params.insert(std::make_pair("height", _interpreter->getDataModel().evalAsString(actualReq.params.find("heightexpr")->second)));
+ }
+ }
+
+ if (actualReq.params.find("width") == actualReq.params.end()) {
+ // no explicit width
+ if (actualReq.params.find("widthexpr") != actualReq.params.end() && _interpreter->getDataModel()) {
+ actualReq.params.insert(std::make_pair("width", _interpreter->getDataModel().evalAsString(actualReq.params.find("widthexpr")->second)));
+ }
+ }
+
+ _workQueue.push(actualReq);
}
void OSGConverter::cancel(const std::string sendId) {
}
void OSGConverter::invoke(const InvokeRequest& req) {
+ int nrThreads = 1;
+ if (req.params.find("threads") != req.params.end() && isNumeric(req.params.find("threads")->second.c_str(), 10)) {
+ nrThreads = strTo<int>(req.params.find("threads")->second);
+ }
+
+ _isRunning = true;
+ for (int i = 0; i < nrThreads; i++) {
+ _threads.insert(new tthread::thread(OSGConverter::run, this));
+ }
+}
+
+void OSGConverter::run(void* instance) {
+ OSGConverter* INSTANCE = (OSGConverter*)instance;
+ while(true) {
+ SendRequest req = INSTANCE->_workQueue.pop();
+ if (INSTANCE->_isRunning) {
+ INSTANCE->process(req);
+ } else {
+ return;
+ }
+ }
+}
+
+void OSGConverter::process(const SendRequest& req) {
+
+ int width = (req.params.find("width") != req.params.end() ? strTo<int>(req.params.find("width")->second) : 640);
+ int height = (req.params.find("height") != req.params.end() ? strTo<int>(req.params.find("height")->second) : 480);
+
+ assert(req.params.find("source") != req.params.end());
+ assert(req.params.find("dest") != req.params.end());
+ assert(req.params.find("format") != req.params.end());
+
+ std::string source = req.params.find("source")->second;
+ std::string dest = req.params.find("dest")->second;
+ std::string format = req.params.find("format")->second;
+
+ osg::ref_ptr<osg::Node> sceneGraph = setupGraph(source);
+
+ osgDB::ReaderWriter::WriteResult result;
+ if (osgDB::Registry::instance()->getReaderWriterForExtension(format) != NULL) {
+ // write as another 3D file
+ result = osgDB::Registry::instance()->writeNode(*sceneGraph, dest, osgDB::Registry::instance()->getOptions());
+ }
+
+ if (result.error()) {
+ // make a screenshot
+ osgViewer::ScreenCaptureHandler::CaptureOperation* cOp = new NameRespectingWriteToFile(dest,
+ format,
+ osgViewer::ScreenCaptureHandler::WriteToFile::OVERWRITE
+ );
+
+ osgViewer::ScreenCaptureHandler* captureHandler = new osgViewer::ScreenCaptureHandler(cOp, -1);
+
+ osgViewer::Viewer viewer;
+ viewer.setSceneData(sceneGraph);
+ viewer.setCameraManipulator(new osgGA::TrackballManipulator());
+ viewer.addEventHandler(captureHandler);
+ captureHandler->startCapture();
+
+ osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
+ osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(ds);
+ traits->width = width;
+ traits->height = height;
+ traits->pbuffer = true;
+ osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
+ GLenum pbuffer = gc->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
+
+ viewer.getCamera()->setGraphicsContext(gc.get());
+ viewer.getCamera()->setViewport(new osg::Viewport(0,0,traits->width,traits->height));
+ viewer.getCamera()->setDrawBuffer(pbuffer);
+ viewer.getCamera()->setReadBuffer(pbuffer);
+
+ // set background color
+ viewer.getCamera()->setClearColor(osg::Vec4f(1.0f,1.0f,1.0f,1.0f));
+ viewer.getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ ((osg::MatrixTransform*)sceneGraph.get())->setMatrix(requestToModelPose(req));
+ viewer.getCamera()->setViewMatrix(requestToCamPose(req));
+
+// viewer.home();
+
+ // perform one viewer iteration
+ viewer.realize();
+ viewer.frame();
+ }
+}
+
+osg::Matrix OSGConverter::requestToModelPose(const SendRequest& req) {
+ double pitch = (req.params.find("pitch") != req.params.end() ? strTo<int>(req.params.find("pitch")->second) : 0);
+ double roll = (req.params.find("roll") != req.params.end() ? strTo<int>(req.params.find("roll")->second) : 0);
+ double yaw = (req.params.find("yaw") != req.params.end() ? strTo<int>(req.params.find("yaw")->second) : 0);
+
+ return eulerToMatrix(pitch, roll, yaw);
+// osg::Matrix m;
+// m.makeIdentity();
+// return m;
+}
+
+osg::Matrix OSGConverter::requestToCamPose(const SendRequest& req) {
+ return eulerToMatrix(0, 0, 0);
+}
+
+osg::ref_ptr<osg::Node> OSGConverter::setupGraph(const std::string filename) {
+
+ /**
+ * root (model pose)
+ * - modelCenter (center model)
+ * - model (actual model)
+ */
+
+ long now = tthread::chrono::system_clock::now();
+
+ {
+ // get some privacy
+ tthread::lock_guard<tthread::recursive_mutex> lock(_cacheMutex);
+
+ // do we have it in the cache?
+ if (_models.find(filename) == _models.end()) {
+ osg::ref_ptr<osg::Node> model = osgDB::readNodeFile(filename);
+ if (!model.valid()) {
+ LOG(ERROR) << "Cannot load model from " << filename;
+ return new osg::MatrixTransform();
+ }
+ _models[filename] = std::make_pair(now, model);
+ }
+
+ // remove old models from cache
+ std::map<std::string, std::pair<long, osg::ref_ptr<osg::Node> > >::iterator modelIter = _models.begin();
+ while(modelIter != _models.end()) {
+ // delete every model unused for 1 minutes
+ if (now - modelIter->second.first > 60000) {
+ _models.erase(modelIter++);
+ } else {
+ modelIter++;
+ }
+ }
+ }
+
+ osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform();
+
+ osg::ref_ptr<osg::Node> model = _models[filename].second;
+ _models[filename].first = now;
+
+ // translation matrix to move model into center
+ osg::ref_ptr<osg::MatrixTransform> modelCenter = new osg::MatrixTransform();
+ modelCenter->addChild(model);
+
+ // move bounding sphere center into origin
+ osg::BoundingSphere bs = model->getBound();
+ modelCenter->setMatrix(osg::Matrix::translate(bs.center() *= -1));
+
+ // add to model pose matrix
+ root->addChild(modelCenter);
+
+ return root;
+}
+
+osg::Matrix OSGConverter::eulerToMatrix(double pitch, double roll, double yaw) {
+ // see http://www.flipcode.com/documents/matrfaq.html#Q36
+ osg::Matrix m;
+ m.makeIdentity();
+
+ double A = cos(pitch);
+ double B = sin(pitch);
+ double C = cos(roll);
+ double D = sin(roll);
+ double E = cos(yaw);
+ double F = sin(yaw);
+
+ double AD = A * D;
+ double BD = B * D;
+
+ m(0,0) = C * E;
+ m(0,1) = -C * F;
+ m(0,2) = -D;
+ m(1,0) = -BD * E + A * F;
+ m(1,1) = BD * F + A * E;
+ m(1,2) = -B * C;
+ m(2,0) = AD * E + B * F;
+ m(2,1) = -AD * F + B * E;
+ m(2,2) = A * C;
+
+ m(0,3) = m(1,3) = m(2,3) = m(3,0) = m(3,1) = m(3,2) = 0;
+ m(3,3) = 1;
+
+ return m;
+}
+
+void OSGConverter::matrixToEuler(const osg::Matrix& m, double& pitch, double& roll, double& yaw) {
+ // see: http://www.flipcode.com/documents/matrfaq.html#Q37
+ double angle_x, angle_z;
+ double D = -1 * asin(m(0,2)); /* Calculate Y-axis angle */
+ double angle_y = D;
+ double C = cos(angle_y);
+
+ /* Gimball lock? */
+ if ( fabs( C ) > 0.005 ) {
+ double tr_x = m(2,2) / C; /* No, so get X-axis angle */
+ double tr_y = -1 * m(1,2) / C;
+ angle_x = atan2( tr_y, tr_x );
+ tr_x = m(0,0) / C; /* Get Z-axis angle */
+ tr_y = -1 * m(0,1) / C;
+ angle_z = atan2( tr_y, tr_x );
+ } else {
+ /* Gimball lock has occurred */
+ angle_x = 0; /* Set X-axis angle to zero */
+ double tr_x = m(1,1); /* And calculate Z-axis angle */
+ double tr_y = m(1,0);
+ angle_z = atan2( tr_y, tr_x );
+ }
+
+ pitch = fmod(angle_x, 2 * M_PI ); /* Clamp all angles to range */
+ roll = fmod( angle_y, 2 * M_PI );
+ yaw = fmod( angle_z, 2 * M_PI );
+}
+
+void OSGConverter::NameRespectingWriteToFile::operator()(const osg::Image& image, const unsigned int context_id) {
+ osgDB::writeImageFile(image, _filename);
}
} \ No newline at end of file
diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.h b/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.h
index c52b1ee..e96a4e9 100644
--- a/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.h
+++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGConverter.h
@@ -2,6 +2,8 @@
#define OSGCONVERTER_H_W09J90F0
#include <uscxml/Interpreter.h>
+#include <osg/Node>
+#include <osgViewer/ViewerEventHandlers>
#ifdef BUILD_AS_PLUGINS
#include "uscxml/plugins/Plugins.h"
@@ -29,7 +31,35 @@ public:
virtual void cancel(const std::string sendId);
virtual void invoke(const InvokeRequest& req);
+ osg::Matrix requestToModelPose(const SendRequest& req);
+ osg::Matrix requestToCamPose(const SendRequest& req);
+
+ static osg::Matrix eulerToMatrix(double pitch, double roll, double yaw);
+ static void matrixToEuler(const osg::Matrix& m, double& pitch, double& roll, double& yaw);
+
protected:
+ class NameRespectingWriteToFile : public osgViewer::ScreenCaptureHandler::WriteToFile {
+ public:
+ NameRespectingWriteToFile(const std::string& filename,
+ const std::string& extension,
+ SavePolicy savePolicy) : osgViewer::ScreenCaptureHandler::WriteToFile(filename, extension, savePolicy) {
+ }
+
+ virtual void operator()(const osg::Image& image, const unsigned int context_id);
+ };
+
+ uscxml::concurrency::BlockingQueue<SendRequest> _workQueue;
+ osg::ref_ptr<osg::Node> setupGraph(const std::string filename);
+
+ std::map<std::string, std::pair<long, osg::ref_ptr<osg::Node> > > _models;
+ tthread::recursive_mutex _cacheMutex;
+
+ std::set<tthread::thread*> _threads;
+
+ static void run(void*);
+ void process(const SendRequest& req);
+
+ bool _isRunning;
};
#ifdef BUILD_AS_PLUGINS
diff --git a/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp b/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp
index 78e3bea..753877c 100644
--- a/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp
+++ b/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp
@@ -117,25 +117,16 @@ void HTTPServletInvoker::httpRecvRequest(const HTTPServer::Request& req) {
_requests[toStr((uintptr_t)req.curlReq)] = req;
- Event event;
+ Event event = req;
if (_isInterpreterGlobal) {
- event.name = "http." + req.type;
+ event.name = "http." + event.data.compound["type"].atom;
event.origin = toStr((uintptr_t)req.curlReq);
} else {
event.name = _callback;
event.data.compound["reqId"] = Data(toStr((uintptr_t)req.curlReq), Data::VERBATIM);
}
- std::map<std::string, std::string>::const_iterator headerIter = req.headers.begin();
- while(headerIter != req.headers.end()) {
- event.data.compound["headers"].compound[headerIter->first] = Data(headerIter->second, Data::VERBATIM);
- headerIter++;
- }
-
- event.data.compound["content"] = Data(req.content, Data::VERBATIM);
- event.data.compound["type"] = Data(req.type, Data::VERBATIM);
-
returnEvent(event);
}
diff --git a/src/uscxml/plugins/invoker/http/HTTPServletInvoker.h b/src/uscxml/plugins/invoker/http/HTTPServletInvoker.h
index 4ac87e0..c9fe844 100644
--- a/src/uscxml/plugins/invoker/http/HTTPServletInvoker.h
+++ b/src/uscxml/plugins/invoker/http/HTTPServletInvoker.h
@@ -19,7 +19,7 @@ public:
virtual std::set<std::string> getNames() {
std::set<std::string> names;
- names.insert("httpserver");
+ names.insert("httpservlet");
names.insert("http://uscxml.tk.informatik.tu-darmstadt.de/#httpserver");
return names;
}
diff --git a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp
index 4f7d61d..b7d08c4 100644
--- a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp
+++ b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp
@@ -54,9 +54,10 @@ void USCXMLInvoker::invoke(const InvokeRequest& req) {
}
}
-void USCXMLInvoker::push(Event& event) {
- event.invokeid = _invokeId;
- _parentInterpreter->receive(event);
+void USCXMLInvoker::push(const Event& event) {
+ Event copyEvent(event);
+ copyEvent.invokeid = _invokeId;
+ _parentInterpreter->receive(copyEvent);
}
} \ No newline at end of file
diff --git a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h
index 792cc5d..aedef32 100644
--- a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h
+++ b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h
@@ -23,6 +23,7 @@ public:
virtual boost::shared_ptr<IOProcessorImpl> create(Interpreter* interpreter);
virtual std::set<std::string> getNames() {
std::set<std::string> names;
+ names.insert("scxml");
names.insert("uscxml");
names.insert("http://www.w3.org/TR/scxml");
names.insert("http://www.w3.org/TR/scxml/");
@@ -34,7 +35,7 @@ public:
virtual void cancel(const std::string sendId);
virtual void invoke(const InvokeRequest& req);
- virtual void push(Event& event);
+ virtual void push(const Event& event);
protected:
Interpreter* _invokedInterpreter;
diff --git a/src/uscxml/plugins/ioprocessor/basichttp/libevent/EventIOProcessor.cpp b/src/uscxml/plugins/ioprocessor/basichttp/libevent/EventIOProcessor.cpp
index 1b58785..6c7a8fc 100644
--- a/src/uscxml/plugins/ioprocessor/basichttp/libevent/EventIOProcessor.cpp
+++ b/src/uscxml/plugins/ioprocessor/basichttp/libevent/EventIOProcessor.cpp
@@ -67,10 +67,26 @@ Data EventIOProcessor::getDataModelVariables() {
}
void EventIOProcessor::httpRecvRequest(const HTTPServer::Request& req) {
- Event reqEvent;
+ Event reqEvent = req;
reqEvent.type = Event::EXTERNAL;
bool scxmlStructFound = false;
+ if (reqEvent.data.compound["header"].compound.find("_scxmleventstruct") != reqEvent.data.compound["header"].compound.end()) {
+ // TODO: this looses all other information
+ reqEvent = Event::fromXML(evhttp_decode_uri(reqEvent.data.compound["header"].compound["_scxmleventstruct"].atom.c_str()));
+ scxmlStructFound = true;
+ }
+ if (reqEvent.data.compound["header"].compound.find("_scxmleventname") != reqEvent.data.compound["header"].compound.end()) {
+ reqEvent.name = evhttp_decode_uri(reqEvent.data.compound["header"].compound["_scxmleventname"].atom.c_str());
+ }
+
+ std::map<std::string, Data>::iterator headerIter = reqEvent.data.compound["header"].compound.begin();
+ while(headerIter != reqEvent.data.compound["header"].compound.end()) {
+ reqEvent.data.compound[headerIter->first] = Data(evhttp_decode_uri(headerIter->second.atom.c_str()), Data::VERBATIM);
+ headerIter++;
+ }
+
+#if 0
std::map<std::string, std::string>::const_iterator headerIter = req.headers.begin();
while(headerIter != req.headers.end()) {
if (boost::iequals("_scxmleventstruct", headerIter->first)) {
@@ -84,6 +100,7 @@ void EventIOProcessor::httpRecvRequest(const HTTPServer::Request& req) {
}
headerIter++;
}
+#endif
if (reqEvent.name.length() == 0)
reqEvent.name = req.type;
diff --git a/src/uscxml/server/HTTPServer.cpp b/src/uscxml/server/HTTPServer.cpp
index fccc8a9..d8a6474 100644
--- a/src/uscxml/server/HTTPServer.cpp
+++ b/src/uscxml/server/HTTPServer.cpp
@@ -5,6 +5,7 @@
#include "uscxml/server/HTTPServer.h"
#include "uscxml/Message.h"
+#include "uscxml/Factory.h"
#include <iostream>
#include <event2/dns.h>
#include <event2/event.h>
@@ -14,7 +15,8 @@
#include <event2/http_struct.h>
#include <event2/thread.h>
-#include <string.h>
+#include <string>
+#include <iostream>
#include <glog/logging.h>
#include <boost/algorithm/string.hpp>
@@ -39,6 +41,9 @@ HTTPServer::HTTPServer(unsigned short port) {
_port++;
}
determineAddress();
+
+ // generic callback
+ evhttp_set_gencb(_http, HTTPServer::httpRecvReqCallback, NULL);
}
HTTPServer::~HTTPServer() {
@@ -71,34 +76,34 @@ void HTTPServer::httpRecvReqCallback(struct evhttp_request *req, void *callbackD
switch (evhttp_request_get_command(req)) {
case EVHTTP_REQ_GET:
- request.type = "GET";
+ request.data.compound["type"] = Data("get", Data::VERBATIM);
break;
case EVHTTP_REQ_POST:
- request.type = "POST";
+ request.data.compound["type"] = Data("post", Data::VERBATIM);
break;
case EVHTTP_REQ_HEAD:
- request.type = "HEAD";
+ request.data.compound["type"] = Data("head", Data::VERBATIM);
break;
case EVHTTP_REQ_PUT:
- request.type = "PUT";
+ request.data.compound["type"] = Data("put", Data::VERBATIM);
break;
case EVHTTP_REQ_DELETE:
- request.type = "DELETE";
+ request.data.compound["type"] = Data("delete", Data::VERBATIM);
break;
case EVHTTP_REQ_OPTIONS:
- request.type = "OPTIONS";
+ request.data.compound["type"] = Data("options", Data::VERBATIM);
break;
case EVHTTP_REQ_TRACE:
- request.type = "TRACE";
+ request.data.compound["type"] = Data("trace", Data::VERBATIM);
break;
case EVHTTP_REQ_CONNECT:
- request.type = "CONNECT";
+ request.data.compound["type"] = Data("connect", Data::VERBATIM);
break;
case EVHTTP_REQ_PATCH:
- request.type = "PATCH";
+ request.data.compound["type"] = Data("patch", Data::VERBATIM);
break;
default:
- request.type = "unknown";
+ request.data.compound["type"] = Data("unknown", Data::VERBATIM);
break;
}
@@ -106,17 +111,38 @@ void HTTPServer::httpRecvReqCallback(struct evhttp_request *req, void *callbackD
struct evkeyval *header;
struct evbuffer *buf;
- // map headers to event structure
+ // insert headers to event data
headers = evhttp_request_get_input_headers(req);
for (header = headers->tqh_first; header; header = header->next.tqe_next) {
- request.headers[header->key] = header->value;
+ request.data.compound["header"].compound[header->key] = Data(header->value, Data::VERBATIM);
}
- request.remoteHost = req->remote_host;
- request.remotePort = req->remote_port;
- request.httpMajor = req->major;
- request.httpMinor = req->minor;
- request.uri = req->uri;
+ request.data.compound["remoteHost"] = Data(req->remote_host, Data::VERBATIM);
+ request.data.compound["remotePort"] = Data(toStr(req->remote_port), Data::VERBATIM);
+ request.data.compound["httpMajor"] = Data(toStr((unsigned short)req->major), Data::VERBATIM);
+ request.data.compound["httpMinor"] = Data(toStr((unsigned short)req->minor), Data::VERBATIM);
+ request.data.compound["uri"] = Data(HTTPServer::getBaseURL() + req->uri, Data::VERBATIM);
+ request.data.compound["path"] = Data(evhttp_uri_get_path(evhttp_request_get_evhttp_uri(req)), Data::VERBATIM);
+
+ // seperate path into components
+ std::stringstream ss(request.data.compound["path"].atom);
+ std::string item;
+ while(std::getline(ss, item, '/')) {
+ if (item.length() == 0)
+ continue;
+ request.data.compound["pathComponent"].array.push_back(Data(item, Data::VERBATIM));
+ }
+
+ // parse query string
+ struct evkeyvalq params;
+ struct evkeyval *param;
+ const char* query = evhttp_uri_get_query(evhttp_request_get_evhttp_uri(req));
+
+ evhttp_parse_query_str(query, &params);
+ for (param = params.tqh_first; param; param = param->next.tqe_next) {
+ request.data.compound["query"].compound[param->key] = Data(param->value, Data::VERBATIM);
+ }
+ evhttp_clear_headers(&params);
// get content
buf = evhttp_request_get_input_buffer(req);
@@ -129,7 +155,40 @@ void HTTPServer::httpRecvReqCallback(struct evhttp_request *req, void *callbackD
}
}
- ((HTTPServlet*)callbackData)->httpRecvRequest(request);
+ if (callbackData == NULL) {
+ HTTPServer::getInstance()->processByMatchingServlet(request);
+ } else {
+ ((HTTPServlet*)callbackData)->httpRecvRequest(request);
+ }
+}
+
+void HTTPServer::processByMatchingServlet(const Request& request) {
+ servlet_iter_t servletIter = _servlets.begin();
+
+ std::string actualPath = request.data.compound.at("path").atom;
+ HTTPServlet* bestMatch = NULL;
+ std::string bestPath;
+
+ while(servletIter != _servlets.end()) {
+ // is the servlet path a prefix of the actual path?
+ std::string servletPath = "/" + servletIter->first;
+ if (boost::iequals(actualPath.substr(0, servletPath.length()), servletPath) && // actual path is a prefix
+ boost::iequals(actualPath.substr(servletPath.length(), 1), "/")) { // and next character is a '/'
+ if (bestPath.length() < servletPath.length()) {
+ // this servlet is a better match
+ bestPath = servletPath;
+ bestMatch = servletIter->second;
+ }
+ }
+ servletIter++;
+ }
+
+ if (bestMatch != NULL) {
+ bestMatch->httpRecvRequest(request);
+ } else {
+ LOG(INFO) << "Got an HTTP request at " << actualPath << " but no servlet is registered there or at a prefix";
+ evhttp_send_error(request.curlReq, 404, NULL);
+ }
}
void HTTPServer::reply(const Reply& reply) {
@@ -154,18 +213,6 @@ bool HTTPServer::registerServlet(const std::string& path, HTTPServlet* servlet)
HTTPServer* INSTANCE = getInstance();
tthread::lock_guard<tthread::recursive_mutex> lock(INSTANCE->_mutex);
- /**
- * Determine path for interpreter.
- *
- * If the interpreter has a name and it is not yet taken, choose it as the path
- * for requests. If the interpreters name path is already taken, append digits
- * until we have an available path.
- *
- * If the interpreter does not specify a name, take its sessionid.
- *
- * Responsibility moved to individual servlets.
- */
-
if(INSTANCE->_servlets.find(path) != INSTANCE->_servlets.end()) {
return false;
}
@@ -182,9 +229,13 @@ bool HTTPServer::registerServlet(const std::string& path, HTTPServlet* servlet)
evhttp_set_cb(INSTANCE->_http, ("/" + path).c_str(), HTTPServer::httpRecvReqCallback, servlet);
return true;
- // generic callback
-// evhttp_set_cb(THIS->_http, "/", EventIOProcessor::httpRecvReq, processor);
-// evhttp_set_gencb(THIS->_http, EventIOProcessor::httpRecvReq, NULL);
+}
+
+std::string HTTPServer::getBaseURL() {
+ HTTPServer* INSTANCE = getInstance();
+ std::stringstream servletURL;
+ servletURL << "http://" << INSTANCE->_address << ":" << INSTANCE->_port;
+ return servletURL.str();
}
void HTTPServer::unregisterServlet(HTTPServlet* servlet) {
diff --git a/src/uscxml/server/HTTPServer.h b/src/uscxml/server/HTTPServer.h
index a387e1f..1ec28c7 100644
--- a/src/uscxml/server/HTTPServer.h
+++ b/src/uscxml/server/HTTPServer.h
@@ -7,6 +7,7 @@
#include <event2/http.h>
#include "uscxml/concurrency/tinythread.h"
+#include "uscxml/Message.h"
namespace uscxml {
@@ -14,23 +15,16 @@ class HTTPServlet;
class HTTPServer {
public:
- class Request {
+ class Request : public Event {
public:
Request() : curlReq(NULL) {}
- std::string type;
- std::map<std::string, std::string> headers;
std::string content;
- std::string remoteHost;
- unsigned short remotePort;
- std::string httpMajor;
- std::string httpMinor;
- std::string uri;
struct evhttp_request* curlReq;
};
class Reply {
public:
- Reply(Request req) : status(200), type(req.type), curlReq(req.curlReq) {}
+ Reply(Request req) : status(200), type(req.data.compound["type"].atom), curlReq(req.curlReq) {}
int status;
std::string type;
std::map<std::string, std::string> headers;
@@ -44,6 +38,8 @@ public:
};
static HTTPServer* getInstance(int port = 8080);
+ static std::string getBaseURL();
+
static void reply(const Reply& reply);
static bool registerServlet(const std::string& path, HTTPServlet* servlet); ///< Register a servlet, returns false if path is already taken
@@ -60,6 +56,8 @@ private:
void determineAddress();
static void httpRecvReqCallback(struct evhttp_request *req, void *callbackData);
+ void processByMatchingServlet(const Request& request);
+
std::map<std::string, HTTPServlet*> _servlets;
typedef std::map<std::string, HTTPServlet*>::iterator servlet_iter_t;
diff --git a/test/samples/uscxml/test-osgconvert.scxml b/test/samples/uscxml/test-osgconvert.scxml
new file mode 100644
index 0000000..90591eb
--- /dev/null
+++ b/test/samples/uscxml/test-osgconvert.scxml
@@ -0,0 +1,40 @@
+<scxml datamodel="ecmascript" name="vrml">
+ <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" />
+ <script>
+ var files = {};
+ </script>
+ <state id="main">
+ <!-- Stop processing if no vrml-path was given on command line -->
+ <transition target="final" cond="_x['args']['vrml-path'] == undefined || _x['args']['vrml-path'].length == 0">
+ <log expr="'No --vrml-path given'" />
+ </transition>
+
+ <!-- Stop processing if an error occurred -->
+ <transition target="final" event="error">
+ <log expr="'An error occured:'" />
+ <script>dump(_event);</script>
+ </transition>
+
+ <!-- Start the directory monitor -->
+ <invoke type="dirmon" id="dirmon.vrml">
+ <param name="dir" expr="_x['args']['vrml-path']" />
+ <param name="recurse" expr="true" />
+ <param name="suffix" expr="'.wrl'" />
+ <param name="reportExisting" expr="true" />
+ <finalize>
+ <send target="#_osgvonvert.osgb">
+ <param name="source" expr="_event.data.file.path" />
+ <param name="dest" expr="_event.data.file.path + '.png'" />
+ </send>
+ </finalize>
+ </invoke>
+
+ <!-- Start the osgconvert invoker to transform 3D files -->
+ <invoke type="osgconvert" id="osgvonvert.osgb">
+ <param name="threads" expr="1" />
+ </invoke>
+
+ <state id="idle" />
+ </state>
+ <state id="final" final="true" />
+</scxml> \ No newline at end of file
diff --git a/test/samples/uscxml/test-postpone-element.scxml b/test/samples/uscxml/test-postpone-element.scxml
new file mode 100644
index 0000000..185f367
--- /dev/null
+++ b/test/samples/uscxml/test-postpone-element.scxml
@@ -0,0 +1,41 @@
+<scxml datamodel="ecmascript" name="postpone">
+ <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" />
+ <script>
+ var blocked = true;
+ </script>
+ <state id="idle">
+
+ <!-- request will be postponed until someone unblocks -->
+ <transition event="http" target="idle" cond="_event.data.pathComponent[1] == 'block'">
+ <if cond="blocked">
+ <log expr="'Postponing'" />
+ <postpone until="blocked == false;" />
+ <else>
+ <response status="200" requestexpr="_event.origin">
+ <content expr="blocked" />
+ </response>
+ </else>
+ </if>
+ </transition>
+
+ <!-- unblock - this triggers all postponed events -->
+ <transition event="http" target="idle" cond="_event.data.pathComponent[1] == 'unblock'">
+ <log expr="'Unblocking'" />
+ <script>blocked = false;</script>
+ <response status="200" requestexpr="_event.origin">
+ <content expr="blocked" />
+ </response>
+ </transition>
+
+ <!-- default -->
+ <transition event="http" target="idle" cond="
+ _event.data.pathComponent[1] != 'unblock' &amp;&amp;
+ _event.data.pathComponent[1] != 'block'
+ ">
+ <script>dump(_event);</script>
+ <response status="200" requestexpr="_event.origin" />
+ </transition>
+
+ </state>
+ <state id="final" final="true" />
+</scxml> \ No newline at end of file
diff --git a/test/samples/uscxml/test-response-element.scxml b/test/samples/uscxml/test-response-element.scxml
index d3d3c53..eb02320 100644
--- a/test/samples/uscxml/test-response-element.scxml
+++ b/test/samples/uscxml/test-response-element.scxml
@@ -3,9 +3,9 @@
<state id="start">
<state id="loop">
<transition event="http" target="loop">
- <!-- script>
+ <script>
dump(_event);
- </script -->
+ </script>
<response status="200" requestexpr="_event.origin" />
</transition>
</state>