From 49c3c43d18c9cce6de305aae77cc8bd839506129 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Mon, 25 Feb 2013 13:28:05 +0100 Subject: Introduced postpone element and reorganized http request representation as events --- CMakeLists.txt | 80 +++- README.md | 37 +- apps/samples/vrml-server.scxml | 103 ++-- config.h.in | 1 + contrib/cmake/FindFFMPEG.cmake | 79 ++++ docs/BUILDING.md | 231 +++++++++ docs/EXTENSIONS.md | 37 ++ src/uscxml/Factory.cpp | 16 + src/uscxml/Interpreter.cpp | 57 ++- src/uscxml/Interpreter.h | 25 +- src/uscxml/Message.h | 3 + src/uscxml/URL.cpp | 1 - src/uscxml/URL.cpp.old | 519 --------------------- src/uscxml/URL.h.old | 154 ------ src/uscxml/concurrency/BlockingQueue.h | 8 +- src/uscxml/concurrency/tinythread.cpp | 20 + src/uscxml/concurrency/tinythread.h | 5 + .../plugins/element/postpone/PostponeElement.cpp | 97 ++++ .../plugins/element/postpone/PostponeElement.h | 62 +++ .../plugins/element/response/ResponseElement.cpp | 38 +- .../plugins/invoker/ffmpeg/FFMPEGInvoker.cpp | 48 ++ src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h | 40 ++ .../invoker/filesystem/dirmon/DirMonInvoker.cpp | 33 +- .../graphics/openscenegraph/OSGConverter.cpp | 300 +++++++++++- .../invoker/graphics/openscenegraph/OSGConverter.h | 30 ++ .../plugins/invoker/http/HTTPServletInvoker.cpp | 13 +- .../plugins/invoker/http/HTTPServletInvoker.h | 2 +- src/uscxml/plugins/invoker/scxml/USCXMLInvoker.cpp | 7 +- src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h | 3 +- .../basichttp/libevent/EventIOProcessor.cpp | 19 +- src/uscxml/server/HTTPServer.cpp | 119 +++-- src/uscxml/server/HTTPServer.h | 16 +- test/samples/uscxml/test-osgconvert.scxml | 40 ++ test/samples/uscxml/test-postpone-element.scxml | 41 ++ test/samples/uscxml/test-response-element.scxml | 4 +- 35 files changed, 1447 insertions(+), 841 deletions(-) create mode 100644 contrib/cmake/FindFFMPEG.cmake create mode 100644 docs/BUILDING.md create mode 100644 docs/EXTENSIONS.md delete mode 100644 src/uscxml/URL.cpp.old delete mode 100644 src/uscxml/URL.h.old create mode 100644 src/uscxml/plugins/element/postpone/PostponeElement.cpp create mode 100644 src/uscxml/plugins/element/postpone/PostponeElement.h create mode 100644 src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp create mode 100644 src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h create mode 100644 test/samples/uscxml/test-osgconvert.scxml create mode 100644 test/samples/uscxml/test-postpone-element.scxml 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 Linux, +Windows and MacOSX, 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. + * Datamodels + * ECMAScript using Google's v8 and JavaScriptCore (JSC is incomplete) + * Prolog using SWI prolog + * Invokers + * scxml: Invoke a nested scxml interpreter + * dirmon: Watches a directory for changes to files + * scenegraph: Simplified 3D scenegraphs with custom markup + * heartbeat: Periodically sends events + * httpservlet: Sends events for http requests to special paths + * umundo: Subscribe to channels and publish events + * DOM + * DOM Core Level 2 + XPath extensions available for ecmascript datamodel + * Namespace aware to embed custom markup for special invokers + * Communication + * Features the standard basichttp io-processor + * Can actually respond to HTTP requests with data via <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 @@ + - - - - - - - - - - - - + - - - - - - - + - + - - - - + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ 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 +# Modified for other libraries by Lasse Kärkkäinen +# 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 [Platform Notes](#platform-notes) below to prepare your system. +2. Checkout uscxml into a convenient directory: + + git clone git://github.com/tklab-tud/uscxml.git + +3. Create a new directory for an *out-of-source* build. I usually create sub-directories +in <USCXML_SRC>/build/. +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 cmake --help +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. + +Note: We download pre-compiled versions of most dependencies at cmake configure-time. If you want +to provide you own libraries, remove them from <USCXML_SRC>/contrib/prebuilt/ and provide +your own. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlatformDependencyVersionComment
EveryoneCMake
required
>= 2.8.6The build-system used for uscxml.
libevent
pre-compiled
>= 2.1.xEvent queues with callbacks and the HTTP server.
curl
pre-compiled
>= 7.29.0URL downloads.
v8
pre-compiled
svn checkoutECMAScript datamodel implementation.
SWI Prolog
pre-compiled for unices
>= 6.3.xProlog datamodel implementation.
glog
pre-compiled
>= 0.3.3Logging library.
Arabica
pre-compiled
>= git checkoutXML DOM / XPath / XML Events.
SQLite
optional
>= 3.7.15.2Persistence and sqlite invoker.
OpenSceneGraph
optional
>= 3.1.X3D invokers (scenegraph, osgconvert).
Doxygen
recommended
Used by make docs to generate documentation from source comments.
Mac OSXXCode
required
4.2.1 worksApples SDK with all the toolchains.
MacPorts
recommended
>= 2.0.3Build system for a wide selection of open-source packages.
libxml2
pre-installed
>= 2.6.16Actual XML parser used by Arabica.
Linuxlibxml2
required
>= 2.6.16Actual XML parser used by Arabica.
WindowsVisual Studio 10
required
v10 pro worksAs a student, you can get your version through MSAA.
+ +# Platform Notes + +This section will detail the preparation of the respective platforms to ultimately compile uscxml. + +## Mac OSX + +You will have to install cmake 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 + $ 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 + $ 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 + $ 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 + +Note: 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" + [...] + -- 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 git://github.com/tklab-tud/uscxml.git + 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 /uscxml.sln. +Just open it up to continue in your IDE. + +Note: 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: + + + + + + + + + + +### 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 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 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& 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 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& 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 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& 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 getConfiguration() { return _configuration; } @@ -202,8 +216,9 @@ protected: Arabica::XPath::XPath _xpath; Arabica::XPath::StandardNamespaceContext _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 _nsToPrefix; bool _running; bool _done; @@ -219,6 +234,8 @@ protected: uscxml::concurrency::BlockingQueue* _parentQueue; DelayedEventQueue* _sendQueue; + Event _currEvent; + HTTPServletInvoker* _httpServlet; std::set _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& 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 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 -#include -#include -#include - -#include -#include - -#include -#include -#ifndef WIN32 -#include -#endif -#include -#include - -#include "uscxml/Common.h" -#include "URL.h" - -#include "uscxml/config.h" - -#include /* defines FILENAME_MAX */ -#ifdef _WIN32 -#include -#include -#include -#include -#include -#define getcwd _getcwd -#else -#include -#endif - -#include // mkstemp -#ifdef HAS_UNISTD_H -#include // 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::toLocalFile(const std::string& content, const std::string& suffix) { - boost::shared_ptr urlImpl = boost::shared_ptr(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(); - } - - 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 -#include -#include - -// use arabica URL parser -#include - -#include -#include - -namespace uscxml { - -class URLImpl : public boost::enable_shared_from_this { -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 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 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 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 _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 lock(_mutex); _queue.push_back(elem); _cond.notify_all(); } + virtual void push_front(const T& elem) { + tthread::lock_guard lock(_mutex); + _queue.push_front(elem); + _cond.notify_all(); + } + virtual T pop() { tthread::lock_guard 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 + +#ifdef BUILD_AS_PLUGINS +#include +#endif + +namespace uscxml { + +#ifdef BUILD_AS_PLUGINS +PLUMA_CONNECTOR +bool connect(pluma::Host& host) { + host.add( new PostponeElementProvider() ); + return true; +} +#endif + +boost::shared_ptr PostponeElement::create(Interpreter* interpreter) { + boost::shared_ptr invoker = boost::shared_ptr(new PostponeElement()); + invoker->_interpreter = interpreter; + return invoker; +} + +void PostponeElement::enterElement(const Arabica::DOM::Node& 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& 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 >::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 lock(PostponeElement::Resubmitter::_accessLock); + _instances.erase(interpreter); + delete this; // committing suicide is ok if we are careful +} + +std::map PostponeElement::Resubmitter::_instances; +tthread::recursive_mutex PostponeElement::Resubmitter::_accessLock; + +PostponeElement::Resubmitter* PostponeElement::Resubmitter::getInstance(Interpreter* interpreter) { + tthread::lock_guard 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 +#include + +#ifdef BUILD_AS_PLUGINS +#include "uscxml/plugins/Plugins.h" +#endif + +namespace uscxml { + +class PostponeElement : public ExecutableContentImpl { +public: + PostponeElement() {} + virtual ~PostponeElement() {} + boost::shared_ptr 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& node); + void exitElement(const Arabica::DOM::Node& 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 > _postponedEvents; + static std::map _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 ResponseElement::create(Interpreter* in } void ResponseElement::enterElement(const Arabica::DOM::Node& 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& 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 lock(servlet->getMutex()); @@ -44,19 +42,39 @@ void ResponseElement::enterElement(const Arabica::DOM::Node& 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(statusStr); - - HTTPServer::Request httpReq = servlet->getRequests()[requestId]; - - HTTPServer::Reply httpReply(httpReq); - httpReply.status = status; + httpReply.status = strTo(statusStr);; + + // extract the content + Arabica::XPath::NodeSet 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 + +#include +#include + +#ifdef BUILD_AS_PLUGINS +#include +#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 FFMPEGInvoker::create(Interpreter* interpreter) { + boost::shared_ptr invoker = boost::shared_ptr(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 + +#ifdef BUILD_AS_PLUGINS +#include "uscxml/plugins/Plugins.h" +#endif + +namespace uscxml { + +class FFMPEGInvoker : public InvokerImpl { +public: + FFMPEGInvoker(); + virtual ~FFMPEGInvoker(); + virtual boost::shared_ptr create(Interpreter* interpreter); + + virtual std::set getNames() { + std::set 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 +#include "uscxml/config.h" + #ifdef BUILD_AS_PLUGINS #include #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 +#include "uscxml/config.h" + +#include +#include +#include +#include +#include #ifdef BUILD_AS_PLUGINS #include @@ -15,10 +22,15 @@ bool connect(pluma::Host& host) { } #endif -OSGConverter::OSGConverter() { +OSGConverter::OSGConverter() : _isRunning(false) { } OSGConverter::~OSGConverter() { + _isRunning = false; + std::set::iterator threadIter = _threads.begin(); + while(threadIter != _threads.end()) { + (*threadIter)->join(); + } }; boost::shared_ptr 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(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(req.params.find("width")->second) : 640); + int height = (req.params.find("height") != req.params.end() ? strTo(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 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 traits = new osg::GraphicsContext::Traits(ds); + traits->width = width; + traits->height = height; + traits->pbuffer = true; + osg::ref_ptr 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(req.params.find("pitch")->second) : 0); + double roll = (req.params.find("roll") != req.params.end() ? strTo(req.params.find("roll")->second) : 0); + double yaw = (req.params.find("yaw") != req.params.end() ? strTo(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 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 lock(_cacheMutex); + + // do we have it in the cache? + if (_models.find(filename) == _models.end()) { + osg::ref_ptr 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 > >::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 root = new osg::MatrixTransform(); + + osg::ref_ptr model = _models[filename].second; + _models[filename].first = now; + + // translation matrix to move model into center + osg::ref_ptr 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 +#include +#include #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 _workQueue; + osg::ref_ptr setupGraph(const std::string filename); + + std::map > > _models; + tthread::recursive_mutex _cacheMutex; + + std::set _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::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 getNames() { std::set 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 create(Interpreter* interpreter); virtual std::set getNames() { std::set 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::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::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 #include #include @@ -14,7 +15,8 @@ #include #include -#include +#include +#include #include #include @@ -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, ¶ms); + 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(¶ms); // 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 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 #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 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 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 _servlets; typedef std::map::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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ - + -- cgit v0.12