Compare commits
191 Commits
version/2.
...
version/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1061580118 | ||
|
|
7184828a58 | ||
|
|
0428adff03 | ||
|
|
04246f0a63 | ||
|
|
61df58d651 | ||
|
|
1bd9a440e8 | ||
|
|
d82c8eb945 | ||
|
|
becbad7ea4 | ||
|
|
2335796a28 | ||
|
|
c6330b64f6 | ||
|
|
df57a23512 | ||
|
|
aea71cf0af | ||
|
|
fb54386f8d | ||
|
|
4d931b6109 | ||
|
|
407c7a10f5 | ||
|
|
16fa4d1d17 | ||
|
|
1ecccd43d4 | ||
|
|
2edbb1cf8b | ||
|
|
8dccbe8029 | ||
|
|
031c833e4d | ||
|
|
890cc46825 | ||
|
|
6b0f7e8512 | ||
|
|
7a220921f0 | ||
|
|
7ef9acda5d | ||
|
|
2a6d62e6f7 | ||
|
|
42b50bd7f7 | ||
|
|
2c3f44ae11 | ||
|
|
b7662f16e5 | ||
|
|
5bc535a8d2 | ||
|
|
3ec9c7ae6e | ||
|
|
e326d0c756 | ||
|
|
7c5434a99f | ||
|
|
26d9256ee9 | ||
|
|
053e761d8f | ||
|
|
5861b52cff | ||
|
|
66193d7283 | ||
|
|
dfb07f359e | ||
|
|
6fe1433497 | ||
|
|
01ace109ba | ||
|
|
8e01acaa12 | ||
|
|
7bbec8dbcb | ||
|
|
10375086ed | ||
|
|
dd367daac9 | ||
|
|
27063d0c32 | ||
|
|
80ff0282c3 | ||
|
|
e973e406a3 | ||
|
|
c64776029e | ||
|
|
5d7d0a922d | ||
|
|
c2c96c4219 | ||
|
|
3e9ed08c53 | ||
|
|
b7df9026c0 | ||
|
|
6527c6357b | ||
|
|
fad333256c | ||
|
|
8753811fe1 | ||
|
|
237752ff62 | ||
|
|
743244a69c | ||
|
|
9f89737986 | ||
|
|
508a5c5f66 | ||
|
|
d84394efa0 | ||
|
|
dd613d48bc | ||
|
|
2e8e500d4f | ||
|
|
48f866dcc6 | ||
|
|
ec4d3e4f9b | ||
|
|
38ddfab1e0 | ||
|
|
353b7e4438 | ||
|
|
7076c9a0ff | ||
|
|
2f42a8714c | ||
|
|
fdf6fc32ff | ||
|
|
f2188b33c6 | ||
|
|
f93fead8f2 | ||
|
|
050f3791cc | ||
|
|
94326fd964 | ||
|
|
f26f3b606d | ||
|
|
7cbfa76be4 | ||
|
|
d896e71ae9 | ||
|
|
ca79d99ec4 | ||
|
|
95f77ef81d | ||
|
|
c4e7d26b70 | ||
|
|
ea49a1a07b | ||
|
|
784223c67f | ||
|
|
77aa2c9a9d | ||
|
|
48145fb234 | ||
|
|
49730cadee | ||
|
|
0b197501e1 | ||
|
|
8b0c246a7b | ||
|
|
9c4face1ae | ||
|
|
952e2e6631 | ||
|
|
4766910413 | ||
|
|
3c2f97a8d2 | ||
|
|
f75730f165 | ||
|
|
b596e62a61 | ||
|
|
1349d48339 | ||
|
|
d69ea5fda6 | ||
|
|
9b68062ba7 | ||
|
|
b217019723 | ||
|
|
a37a254a68 | ||
|
|
5258699f20 | ||
|
|
483bebb003 | ||
|
|
8ca8052a8d | ||
|
|
d68b1144b8 | ||
|
|
42c39b6be3 | ||
|
|
bb82b9d168 | ||
|
|
b1f865d461 | ||
|
|
8e75c6be50 | ||
|
|
06a5f9188d | ||
|
|
370a991208 | ||
|
|
6deb77dd4d | ||
|
|
9e3172cb04 | ||
|
|
426c6b9a72 | ||
|
|
d148bdedcd | ||
|
|
23140e3bf7 | ||
|
|
2f023803e7 | ||
|
|
d658b5fc38 | ||
|
|
68cd84c330 | ||
|
|
0bd82a43d3 | ||
|
|
7fdf42b699 | ||
|
|
aeb0e9aac3 | ||
|
|
1099a3bdf0 | ||
|
|
aa3458f69c | ||
|
|
0186cbb7b7 | ||
|
|
fa36e94c4b | ||
|
|
ad83e70cf5 | ||
|
|
4a0377c0a1 | ||
|
|
83a3241830 | ||
|
|
f299b351e3 | ||
|
|
3880e4ef47 | ||
|
|
f205e918d9 | ||
|
|
42b0b9306b | ||
|
|
483659c319 | ||
|
|
add14dd27c | ||
|
|
61516a5e97 | ||
|
|
17418039e1 | ||
|
|
25cae61211 | ||
|
|
db98c7440e | ||
|
|
3783ae2234 | ||
|
|
3e8732b230 | ||
|
|
b39bca9458 | ||
|
|
d263334030 | ||
|
|
b2cea62189 | ||
|
|
d6b886c69b | ||
|
|
38fb9ea41e | ||
|
|
a922aaa68e | ||
|
|
4f2e36ca46 | ||
|
|
f367627cac | ||
|
|
7a7fcf10ad | ||
|
|
b9bd2734eb | ||
|
|
9c7bd4f5d5 | ||
|
|
362d47f91f | ||
|
|
a18792c397 | ||
|
|
0e2ddb2f16 | ||
|
|
bcfadd6657 | ||
|
|
021cf1a2b4 | ||
|
|
acf86be516 | ||
|
|
dd93eb88f2 | ||
|
|
e06f9e35b3 | ||
|
|
c0424aba7e | ||
|
|
b468254d73 | ||
|
|
15e3e92ec2 | ||
|
|
ee403fd83a | ||
|
|
05f266ac10 | ||
|
|
f52c0026dd | ||
|
|
a284107249 | ||
|
|
a2e25e7940 | ||
|
|
d45a0161cf | ||
|
|
c7a5d15e7a | ||
|
|
3cb2241b1d | ||
|
|
b236821661 | ||
|
|
fc75b0bd21 | ||
|
|
a9fc84d568 | ||
|
|
b15c6d887d | ||
|
|
c2f89978ff | ||
|
|
a004153223 | ||
|
|
dde2bbcbe7 | ||
|
|
7bd572f3ec | ||
|
|
486011dceb | ||
|
|
01c45633b7 | ||
|
|
5320d0ecaa | ||
|
|
757ba03918 | ||
|
|
948db69cc9 | ||
|
|
92f363926e | ||
|
|
d0ff144753 | ||
|
|
943efbb3ed | ||
|
|
6962de4b1f | ||
|
|
6f7c0c23d1 | ||
|
|
14eccc70da | ||
|
|
d4b48cec5d | ||
|
|
2d23c5351f | ||
|
|
b5dfaf170a | ||
|
|
c372591f36 | ||
|
|
36d1308aa6 | ||
|
|
b0d6ed3844 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.*
|
||||
*~
|
||||
Makefile
|
||||
*.o
|
||||
lib*.a
|
||||
|
||||
4
3rdparty/CMakeLists.txt
vendored
Normal file
4
3rdparty/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
if (NOT SYSTEM_EXPAT)
|
||||
add_subdirectory(expat)
|
||||
endif()
|
||||
|
||||
33
3rdparty/expat/CMakeLists.txt
vendored
Normal file
33
3rdparty/expat/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
configure_file (
|
||||
"${PROJECT_SOURCE_DIR}/3rdparty/expat/expat_config_cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/3rdparty/expat/expat_config.h"
|
||||
)
|
||||
|
||||
set(expat_sources
|
||||
asciitab.h
|
||||
hashtable.h
|
||||
iasciitab.h
|
||||
latin1tab.h
|
||||
nametab.h
|
||||
utf8tab.h
|
||||
xmldef.h
|
||||
xmlparse.h
|
||||
xmlrole.h
|
||||
xmltok.h
|
||||
xmltok_impl.h
|
||||
hashtable.c
|
||||
xmlparse.c
|
||||
xmlrole.c
|
||||
xmltok.c
|
||||
internal.h
|
||||
ascii.h
|
||||
sg_expat.h
|
||||
sg_expat_external.h
|
||||
)
|
||||
|
||||
foreach(s ${expat_sources})
|
||||
set_property(GLOBAL
|
||||
APPEND PROPERTY LOCAL_EXPAT_SOURCES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${s}")
|
||||
endforeach()
|
||||
@@ -15,7 +15,7 @@
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "expat_external.h"
|
||||
#include "sg_expat_external.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -23,7 +23,7 @@
|
||||
#endif /* ndef COMPILED_FROM_DSP */
|
||||
|
||||
#include "ascii.h"
|
||||
#include "expat.h"
|
||||
#include "sg_expat.h"
|
||||
|
||||
#ifdef XML_UNICODE
|
||||
#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX
|
||||
@@ -18,7 +18,7 @@
|
||||
#endif
|
||||
#endif /* ndef COMPILED_FROM_DSP */
|
||||
|
||||
#include "expat_external.h"
|
||||
#include "sg_expat_external.h"
|
||||
#include "internal.h"
|
||||
#include "xmlrole.h"
|
||||
#include "ascii.h"
|
||||
@@ -18,7 +18,7 @@
|
||||
#endif
|
||||
#endif /* ndef COMPILED_FROM_DSP */
|
||||
|
||||
#include "expat_external.h"
|
||||
#include "sg_expat_external.h"
|
||||
#include "internal.h"
|
||||
#include "xmltok.h"
|
||||
#include "nametab.h"
|
||||
144
CMakeLists.txt
144
CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.6.4)
|
||||
include (CheckFunctionExists)
|
||||
include (CheckIncludeFile)
|
||||
include (CheckCXXSourceCompiles)
|
||||
|
||||
include (CheckCXXCompilerFlag)
|
||||
|
||||
project(SimGear)
|
||||
|
||||
@@ -10,6 +10,8 @@ project(SimGear)
|
||||
file(READ version versionFile)
|
||||
string(STRIP ${versionFile} SIMGEAR_VERSION)
|
||||
|
||||
set(FIND_LIBRARY_USE_LIB64_PATHS ON)
|
||||
|
||||
# use simgear version also as the SO version (if building SOs)
|
||||
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
|
||||
|
||||
@@ -21,33 +23,31 @@ if(InSourceBuild)
|
||||
message(WARNING " mkdir ../sgbuild && cd ../sgbuild && cmake ${CMAKE_SOURCE_DIR}")
|
||||
endif(InSourceBuild)
|
||||
|
||||
if (NOT EMBEDDED_SIMGEAR)
|
||||
#packaging
|
||||
SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING")
|
||||
SET(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simulation support libraries for FlightGear and related projects")
|
||||
SET(CPACK_PACKAGE_VENDOR "The FlightGear project")
|
||||
SET(CPACK_GENERATOR "TBZ2")
|
||||
SET(CPACK_INSTALL_CMAKE_PROJECTS ${CMAKE_CURRENT_BINARY_DIR};SimGear;ALL;/)
|
||||
#packaging
|
||||
SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING")
|
||||
SET(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simulation support libraries for FlightGear and related projects")
|
||||
SET(CPACK_PACKAGE_VENDOR "The FlightGear project")
|
||||
SET(CPACK_GENERATOR "TBZ2")
|
||||
SET(CPACK_INSTALL_CMAKE_PROJECTS ${CMAKE_CURRENT_BINARY_DIR};SimGear;ALL;/)
|
||||
|
||||
|
||||
# split version string into components, note CMAKE_MATCH_0 is the entire regexp match
|
||||
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CPACK_PACKAGE_VERSION ${SIMGEAR_VERSION} )
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
# split version string into components, note CMAKE_MATCH_0 is the entire regexp match
|
||||
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CPACK_PACKAGE_VERSION ${SIMGEAR_VERSION} )
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
|
||||
message(STATUS "version is ${CPACK_PACKAGE_VERSION_MAJOR} dot ${CPACK_PACKAGE_VERSION_MINOR} dot ${CPACK_PACKAGE_VERSION_PATCH}")
|
||||
message(STATUS "version is ${CPACK_PACKAGE_VERSION_MAJOR} dot ${CPACK_PACKAGE_VERSION_MINOR} dot ${CPACK_PACKAGE_VERSION_PATCH}")
|
||||
|
||||
set(CPACK_SOURCE_GENERATOR TBZ2)
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "simgear-${SIMGEAR_VERSION}" CACHE INTERNAL "tarball basename")
|
||||
set(CPACK_SOURCE_IGNORE_FILES
|
||||
"^${PROJECT_SOURCE_DIR}/.git;\\\\.gitignore;Makefile.am;~$;${CPACK_SOURCE_IGNORE_FILES}")
|
||||
set(CPACK_SOURCE_GENERATOR TBZ2)
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "simgear-${SIMGEAR_VERSION}" CACHE INTERNAL "tarball basename")
|
||||
set(CPACK_SOURCE_IGNORE_FILES
|
||||
"^${PROJECT_SOURCE_DIR}/.git;\\\\.gitignore;Makefile.am;~$;${CPACK_SOURCE_IGNORE_FILES}")
|
||||
|
||||
message(STATUS "ignoring: ${CPACK_SOURCE_IGNORE_FILES}")
|
||||
|
||||
include (CPack)
|
||||
endif()
|
||||
message(STATUS "ignoring: ${CPACK_SOURCE_IGNORE_FILES}")
|
||||
|
||||
include (CPack)
|
||||
|
||||
# We have some custom .cmake scripts not in the official distribution.
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}")
|
||||
@@ -87,13 +87,6 @@ if(NOT "${CMAKE_LIBRARY_ARCHITECTURE}" STREQUAL "")
|
||||
message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}")
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
# TBD: are these really necessary? Aren't they considered by cmake automatically?
|
||||
list(APPEND ADDITIONAL_LIBRARY_PATHS
|
||||
/opt/local
|
||||
/usr/local
|
||||
/usr)
|
||||
endif()
|
||||
#####################################################################################
|
||||
|
||||
if (NOT MSVC)
|
||||
@@ -108,8 +101,6 @@ endif()
|
||||
|
||||
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
|
||||
option(JPEG_FACTORY "Enable JPEG-factory support" OFF)
|
||||
option(SG_SVN_CLIENT "Set to ON to build SimGear with built-in SVN support" OFF)
|
||||
option(ENABLE_LIBSVN "Set to ON to build SimGear with libsvnclient support" ON)
|
||||
option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF)
|
||||
option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON)
|
||||
option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON)
|
||||
@@ -199,25 +190,6 @@ else()
|
||||
message(STATUS "JPEG-factory: DISABLED")
|
||||
endif(JPEG_FACTORY)
|
||||
|
||||
if (SG_SVN_CLIENT)
|
||||
message(STATUS "Using built-in subversion client code")
|
||||
elseif(ENABLE_LIBSVN)
|
||||
find_package(SvnClient)
|
||||
|
||||
if(LIBSVN_FOUND)
|
||||
message(STATUS "Subversion client support: ENABLED")
|
||||
set(HAVE_SVN_CLIENT_H 1)
|
||||
set(HAVE_LIBSVN_CLIENT_1 1)
|
||||
else()
|
||||
# Oops. ENABLE_LIBSVN is ON, but svn is still missing.
|
||||
# Provide clearly visible warning/hint, so builders know what else they should install (or disable).
|
||||
message(WARNING "Failed to enable subversion client support. Unable to find required subversion client library. Some features may not be available (scenery download).")
|
||||
message(WARNING "Install 'libsvn' library/DLL (libsvn-devel/libsvnclient/...). Otherwise disable subversion support (set 'ENABLE_LIBSVN' to 'OFF').")
|
||||
endif(LIBSVN_FOUND)
|
||||
else()
|
||||
message(STATUS "Subversion client support: DISABLED")
|
||||
endif(SG_SVN_CLIENT)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
@@ -225,12 +197,19 @@ if (SYSTEM_EXPAT)
|
||||
message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true")
|
||||
set(SIMGEAR_SHARED ON)
|
||||
find_package(EXPAT REQUIRED)
|
||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
else()
|
||||
message(STATUS "Using built-in expat code")
|
||||
add_definitions(-DHAVE_EXPAT_CONFIG_H)
|
||||
# XML_STATIC is important to avoid sg_expat_external.h
|
||||
# declaring symbols as declspec(import)
|
||||
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
|
||||
set(EXPAT_INCLUDE_DIRS
|
||||
${PROJECT_SOURCE_DIR}/3rdparty/expat
|
||||
${PROJECT_BINARY_DIR}/3rdparty/expat)
|
||||
endif(SYSTEM_EXPAT)
|
||||
|
||||
include_directories(${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
||||
check_include_file(sys/time.h HAVE_SYS_TIME_H)
|
||||
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
|
||||
@@ -279,6 +258,20 @@ if(HAVE_CLOCK_GETTIME)
|
||||
endif(HAVE_RT)
|
||||
endif(HAVE_CLOCK_GETTIME)
|
||||
|
||||
set(DL_LIBRARY "")
|
||||
check_cxx_source_compiles(
|
||||
"#include <dlfcn.h>
|
||||
int main(void) {
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
HAVE_DLFCN_H)
|
||||
|
||||
if(HAVE_DLFCN_H)
|
||||
check_library_exists(dl dlerror "" HAVE_DL)
|
||||
set(DL_LIBRARY "dl")
|
||||
endif()
|
||||
|
||||
SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually 'd' on windows")
|
||||
SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
|
||||
SET(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
|
||||
@@ -312,6 +305,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(WARNING_FLAGS_C "-Wall")
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
# boost goes haywire wrt static asserts
|
||||
check_cxx_compiler_flag(-Wno-unused-local-typedefs HAS_NOWARN_UNUSED_TYPEDEFS)
|
||||
if(HAS_NOWARN_UNUSED_TYPEDEFS)
|
||||
set(WARNING_FLAGS_CXX " ${WARNING_FLAGS_CXX} -Wno-unused-local-typedefs")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
||||
if(MINGW)
|
||||
@@ -344,13 +345,11 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
|
||||
include_directories(${PROJECT_BINARY_DIR}/simgear)
|
||||
include_directories(${PROJECT_BINARY_DIR}/simgear/xml)
|
||||
|
||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${ZLIB_INCLUDE_DIR}
|
||||
${OPENAL_INCLUDE_DIR}
|
||||
${LibArchive_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
add_definitions(-DHAVE_CONFIG_H)
|
||||
@@ -362,11 +361,6 @@ configure_file (
|
||||
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
|
||||
)
|
||||
|
||||
configure_file (
|
||||
"${PROJECT_SOURCE_DIR}/simgear/xml/expat_config_cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/simgear/xml/expat_config.h"
|
||||
)
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
# enable CTest / make test target
|
||||
message(STATUS "Tests: ENABLED")
|
||||
@@ -378,31 +372,24 @@ else()
|
||||
endif(ENABLE_TESTS)
|
||||
|
||||
# always set TEST_LIBS as it is also used by other tools/applications
|
||||
# TODO maybe better rename?
|
||||
if(SIMGEAR_SHARED)
|
||||
set( TEST_LIBS
|
||||
SimGearCore)
|
||||
else()
|
||||
set( TEST_LIBS
|
||||
SimGearCore
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${ZLIB_LIBRARY}
|
||||
${WINSOCK_LIBRARY}
|
||||
${RT_LIBRARY}
|
||||
${CORE_SERVICES_LIBRARY})
|
||||
endif()
|
||||
set(TEST_LIBS_INTERNAL_CORE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${ZLIB_LIBRARY}
|
||||
${WINSOCK_LIBRARY}
|
||||
${RT_LIBRARY}
|
||||
${DL_LIBRARY}
|
||||
${CORE_SERVICES_LIBRARY})
|
||||
set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
set( TEST_LIBS
|
||||
SimGearScene
|
||||
${TEST_LIBS}
|
||||
${OPENGL_LIBRARIES})
|
||||
set(TEST_LIBS SimGearScene ${OPENGL_LIBRARIES} ${TEST_LIBS})
|
||||
endif()
|
||||
|
||||
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
|
||||
|
||||
add_subdirectory(3rdparty)
|
||||
add_subdirectory(simgear)
|
||||
|
||||
if (NOT EMBEDDED_SIMGEAR)
|
||||
#-----------------------------------------------------------------------------
|
||||
### uninstall target
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -413,5 +400,4 @@ CONFIGURE_FILE(
|
||||
ADD_CUSTOM_TARGET(uninstall
|
||||
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# Find Subversion client libraries, and dependencies
|
||||
# including APR (Apache Portable Runtime)
|
||||
|
||||
include (CheckFunctionExists)
|
||||
include (CheckIncludeFile)
|
||||
include (CheckLibraryExists)
|
||||
|
||||
macro(find_static_component comp libs)
|
||||
# account for alternative Windows svn distribution naming
|
||||
if(MSVC)
|
||||
set(compLib "lib${comp}")
|
||||
else(MSVC)
|
||||
set(compLib "${comp}")
|
||||
endif(MSVC)
|
||||
|
||||
string(TOUPPER "${comp}" compLibBase)
|
||||
set( compLibName ${compLibBase}_LIBRARY )
|
||||
|
||||
# NO_DEFAULT_PATH is important on Mac - we need to ensure subversion
|
||||
# libraires in dist/ or Macports are picked over the Apple version
|
||||
# in /usr, since that's what we will ship.
|
||||
# On other platforms we do need default paths though, i.e. since Linux
|
||||
# distros may use architecture-specific directories (like
|
||||
# /usr/lib/x86_64-linux-gnu) which we cannot hardcode/guess here.
|
||||
FIND_LIBRARY(${compLibName}
|
||||
if(APPLE)
|
||||
NO_DEFAULT_PATH
|
||||
endif(APPLE)
|
||||
NAMES ${compLib}
|
||||
HINTS $ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib
|
||||
PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
|
||||
PATHS ${ADDITIONAL_LIBRARY_PATHS}
|
||||
)
|
||||
|
||||
list(APPEND ${libs} ${${compLibName}})
|
||||
endmacro()
|
||||
|
||||
find_program(HAVE_APR_CONFIG apr-1-config)
|
||||
if(HAVE_APR_CONFIG)
|
||||
|
||||
execute_process(COMMAND apr-1-config --cppflags --includes
|
||||
OUTPUT_VARIABLE APR_CFLAGS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(COMMAND apr-1-config --link-ld
|
||||
OUTPUT_VARIABLE RAW_APR_LIBS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# clean up some vars, or other CMake pieces complain
|
||||
string(STRIP "${RAW_APR_LIBS}" APR_LIBS)
|
||||
|
||||
else(HAVE_APR_CONFIG)
|
||||
message(STATUS "apr-1-config not found, implement manual search for APR")
|
||||
endif(HAVE_APR_CONFIG)
|
||||
|
||||
if(HAVE_APR_CONFIG OR MSVC)
|
||||
find_path(LIBSVN_INCLUDE_DIR svn_client.h
|
||||
NO_DEFAULT_PATH
|
||||
HINTS
|
||||
$ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include
|
||||
PATH_SUFFIXES include/subversion-1
|
||||
PATHS
|
||||
/opt/local
|
||||
/usr/local
|
||||
/usr
|
||||
)
|
||||
|
||||
set(LIBSVN_LIBRARIES "")
|
||||
if (MSVC)
|
||||
find_static_component("apr-1" LIBSVN_LIBRARIES)
|
||||
else (MSVC)
|
||||
list(APPEND LIBSVN_LIBRARIES ${APR_LIBS})
|
||||
endif (MSVC)
|
||||
find_static_component("svn_client-1" LIBSVN_LIBRARIES)
|
||||
find_static_component("svn_subr-1" LIBSVN_LIBRARIES)
|
||||
find_static_component("svn_ra-1" LIBSVN_LIBRARIES)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBSVN DEFAULT_MSG LIBSVN_LIBRARIES LIBSVN_INCLUDE_DIR)
|
||||
if(NOT LIBSVN_FOUND)
|
||||
set(LIBSVN_LIBRARIES "")
|
||||
endif(NOT LIBSVN_FOUND)
|
||||
endif(HAVE_APR_CONFIG OR MSVC)
|
||||
@@ -45,54 +45,29 @@ install (FILES ${HEADERS} DESTINATION include/simgear/)
|
||||
get_property(coreSources GLOBAL PROPERTY CORE_SOURCES)
|
||||
get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES)
|
||||
get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS)
|
||||
|
||||
if(LIBSVN_FOUND)
|
||||
add_definitions(${APR_CFLAGS})
|
||||
include_directories(${LIBSVN_INCLUDE_DIR})
|
||||
endif()
|
||||
get_property(localExpatSources GLOBAL PROPERTY LOCAL_EXPAT_SOURCES)
|
||||
|
||||
if(SIMGEAR_SHARED)
|
||||
message(STATUS "Library building mode: SHARED LIBRARIES")
|
||||
add_library(SimGearCore SHARED ${coreSources})
|
||||
add_library(SimGearCore SHARED ${coreSources} ${localExpatSources})
|
||||
|
||||
# set_property(TARGET SimGearCore PROPERTY FRAMEWORK 1)
|
||||
# message(STATUS "public header: ${publicHeaders}")
|
||||
# set_property(TARGET SimGearCore PROPERTY PUBLIC_HEADER "${publicHeaders}")
|
||||
set_property(TARGET SimGearCore PROPERTY LINKER_LANGUAGE CXX)
|
||||
|
||||
set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION})
|
||||
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
||||
|
||||
target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY}
|
||||
${LibArchive_LIBRARIES}
|
||||
${EXPAT_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CORE_SERVICES_LIBRARY})
|
||||
|
||||
if(LIBSVN_FOUND)
|
||||
target_link_libraries(SimGearCore ${LIBSVN_LIBRARIES})
|
||||
endif(LIBSVN_FOUND)
|
||||
|
||||
install(TARGETS SimGearCore EXPORT SimGearCoreConfig LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(EXPORT SimGearCoreConfig DESTINATION share/SimGearCore)
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
add_library(SimGearScene SHARED ${sceneSources})
|
||||
# set_property(TARGET SimGearScene PROPERTY FRAMEWORK 1)
|
||||
# set_property(TARGET SimGearScene PROPERTY PUBLIC_HEADER "${publicHeaders}")
|
||||
set_property(TARGET SimGearScene PROPERTY LINKER_LANGUAGE CXX)
|
||||
set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION})
|
||||
set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
|
||||
|
||||
target_link_libraries(SimGearScene
|
||||
SimGearCore
|
||||
${ZLIB_LIBRARY}
|
||||
${OPENSCENEGRAPH_LIBRARIES}
|
||||
${OPENAL_LIBRARY}
|
||||
${OPENGL_LIBRARY}
|
||||
${JPEG_LIBRARY})
|
||||
|
||||
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
# EXPORT SimGearSceneConfig
|
||||
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} )
|
||||
# install(EXPORT SimGearSceneConfig DESTINATION share/SimGearScene)
|
||||
endif()
|
||||
|
||||
install(TARGETS SimGearCore LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
else()
|
||||
message(STATUS "Library building mode: STATIC LIBRARIES")
|
||||
|
||||
@@ -114,7 +89,7 @@ else()
|
||||
source_group("${name}\\Headers" FILES ${g2})
|
||||
endforeach()
|
||||
|
||||
add_library(SimGearCore STATIC ${coreSources})
|
||||
add_library(SimGearCore STATIC ${coreSources} ${localExpatSources})
|
||||
install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
@@ -141,6 +116,24 @@ else()
|
||||
endif(NOT SIMGEAR_HEADLESS)
|
||||
endif(SIMGEAR_SHARED)
|
||||
|
||||
target_link_libraries(SimGearCore
|
||||
${ZLIB_LIBRARY}
|
||||
${RT_LIBRARY}
|
||||
${DL_LIBRARY}
|
||||
${EXPAT_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CORE_SERVICES_LIBRARY})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
target_link_libraries(SimGearScene
|
||||
SimGearCore
|
||||
${ZLIB_LIBRARY}
|
||||
${OPENSCENEGRAPH_LIBRARIES}
|
||||
${OPENAL_LIBRARY}
|
||||
${OPENGL_LIBRARY}
|
||||
${JPEG_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(ENABLE_RTI)
|
||||
# Ugly first aid to make hla compile agian
|
||||
set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx
|
||||
|
||||
@@ -212,7 +212,7 @@ std::string SGBucket::gen_base_path() const {
|
||||
main_lat *= -1;
|
||||
}
|
||||
|
||||
sprintf(raw_path, "%c%03d%c%02d/%c%03d%c%02d",
|
||||
snprintf(raw_path, 256, "%c%03d%c%02d/%c%03d%c%02d",
|
||||
hem, top_lon, pole, top_lat,
|
||||
hem, main_lon, pole, main_lat);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ set(HEADERS
|
||||
canvas_fwd.hxx
|
||||
Canvas.hxx
|
||||
CanvasEvent.hxx
|
||||
CanvasEventListener.hxx
|
||||
CanvasEventManager.hxx
|
||||
CanvasEventTypes.hxx
|
||||
CanvasEventVisitor.hxx
|
||||
@@ -20,7 +19,6 @@ set(HEADERS
|
||||
set(SOURCES
|
||||
Canvas.cxx
|
||||
CanvasEvent.cxx
|
||||
CanvasEventListener.cxx
|
||||
CanvasEventManager.cxx
|
||||
CanvasEventVisitor.cxx
|
||||
CanvasMgr.cxx
|
||||
|
||||
@@ -74,6 +74,12 @@ namespace canvas
|
||||
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Canvas::~Canvas()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::onDestroy()
|
||||
{
|
||||
@@ -84,19 +90,6 @@ namespace canvas
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
||||
{
|
||||
_system_adapter = system_adapter;
|
||||
_texture.setSystemAdapter(system_adapter);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
SystemAdapterPtr Canvas::getSystemAdapter() const
|
||||
{
|
||||
return _system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setCanvasMgr(CanvasMgr* canvas_mgr)
|
||||
{
|
||||
@@ -221,15 +214,16 @@ namespace canvas
|
||||
|
||||
osg::Camera* camera = _texture.getCamera();
|
||||
|
||||
// TODO Allow custom render order? For now just keep in order with
|
||||
// property tree.
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER, _node->getIndex());
|
||||
|
||||
osg::Vec4 clear_color(0.0f, 0.0f, 0.0f , 1.0f);
|
||||
parseColor(_node->getStringValue("background"), clear_color);
|
||||
camera->setClearColor(clear_color);
|
||||
|
||||
camera->addChild(_root_group->getMatrixTransform());
|
||||
|
||||
// Ensure objects are drawn in order of traversal
|
||||
camera->getOrCreateStateSet()->setBinName("TraversalOrderBin");
|
||||
|
||||
if( _texture.serviceable() )
|
||||
{
|
||||
setStatusFlags(STATUS_OK);
|
||||
@@ -319,12 +313,13 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
naRef Canvas::addEventListener(const nasal::CallContext& ctx)
|
||||
bool Canvas::addEventListener( const std::string& type,
|
||||
const EventListener& cb )
|
||||
{
|
||||
if( !_root_group.get() )
|
||||
naRuntimeError(ctx.c, "Canvas: No root group!");
|
||||
throw std::runtime_error("Canvas::AddEventListener: no root group!");
|
||||
|
||||
return _root_group->addEventListener(ctx);
|
||||
return _root_group->addEventListener(type, cb);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -419,7 +414,8 @@ namespace canvas
|
||||
|
||||
EventVisitor visitor( EventVisitor::TRAVERSE_DOWN,
|
||||
event->getClientPos(),
|
||||
event->getDelta() );
|
||||
event->getDelta(),
|
||||
_root_group );
|
||||
if( !_root_group->accept(visitor) )
|
||||
return false;
|
||||
|
||||
@@ -585,12 +581,40 @@ namespace canvas
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Canvas::addPlacementFactory: replace existing factor for type " << type
|
||||
"Canvas::addPlacementFactory: replace existing factory '" << type << "'"
|
||||
);
|
||||
|
||||
_placement_factories[type] = factory;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::removePlacementFactory(const std::string& type)
|
||||
{
|
||||
PlacementFactoryMap::iterator it = _placement_factories.find(type);
|
||||
if( it == _placement_factories.end() )
|
||||
SG_LOG
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Canvas::removePlacementFactory: no such factory '" << type << "'"
|
||||
);
|
||||
else
|
||||
_placement_factories.erase(it);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
||||
{
|
||||
_system_adapter = system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
SystemAdapterPtr Canvas::getSystemAdapter()
|
||||
{
|
||||
return _system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Canvas::setSelf(const PropertyBasedElementPtr& self)
|
||||
{
|
||||
@@ -632,6 +656,7 @@ namespace canvas
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Canvas::PlacementFactoryMap Canvas::_placement_factories;
|
||||
SystemAdapterPtr Canvas::_system_adapter;
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace simgear
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <osg/NodeCallback>
|
||||
#include <osg/observer_ptr>
|
||||
|
||||
#include <memory>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace simgear
|
||||
@@ -71,11 +71,9 @@ namespace canvas
|
||||
typedef osg::ref_ptr<CullCallback> CullCallbackPtr;
|
||||
|
||||
Canvas(SGPropertyNode* node);
|
||||
virtual ~Canvas();
|
||||
virtual void onDestroy();
|
||||
|
||||
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
||||
SystemAdapterPtr getSystemAdapter() const;
|
||||
|
||||
void setCanvasMgr(CanvasMgr* canvas_mgr);
|
||||
CanvasMgr* getCanvasMgr() const;
|
||||
|
||||
@@ -132,7 +130,7 @@ namespace canvas
|
||||
|
||||
void update(double delta_time_sec);
|
||||
|
||||
naRef addEventListener(const nasal::CallContext& ctx);
|
||||
bool addEventListener(const std::string& type, const EventListener& cb);
|
||||
|
||||
void setSizeX(int sx);
|
||||
void setSizeY(int sy);
|
||||
@@ -162,13 +160,23 @@ namespace canvas
|
||||
void reloadPlacements( const std::string& type = std::string() );
|
||||
static void addPlacementFactory( const std::string& type,
|
||||
PlacementFactory factory );
|
||||
static void removePlacementFactory(const std::string& type);
|
||||
|
||||
/**
|
||||
* Set global SystemAdapter for all Canvas/ODGauge instances.
|
||||
*
|
||||
* The SystemAdapter is responsible for application specific operations
|
||||
* like loading images/fonts and adding/removing cameras to the scene
|
||||
* graph.
|
||||
*/
|
||||
static void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
||||
static SystemAdapterPtr getSystemAdapter();
|
||||
|
||||
protected:
|
||||
|
||||
SystemAdapterPtr _system_adapter;
|
||||
CanvasMgr *_canvas_mgr;
|
||||
|
||||
std::auto_ptr<EventManager> _event_manager;
|
||||
boost::scoped_ptr<EventManager> _event_manager;
|
||||
|
||||
int _size_x,
|
||||
_size_y,
|
||||
@@ -202,6 +210,8 @@ namespace canvas
|
||||
|
||||
private:
|
||||
|
||||
static SystemAdapterPtr _system_adapter;
|
||||
|
||||
Canvas(const Canvas&); // = delete;
|
||||
Canvas& operator=(const Canvas&); // = delete;
|
||||
};
|
||||
|
||||
@@ -62,6 +62,12 @@ namespace canvas
|
||||
return target;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ElementWeakPtr Event::getCurrentTarget() const
|
||||
{
|
||||
return current_target;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
double Event::getTime() const
|
||||
{
|
||||
|
||||
@@ -41,7 +41,8 @@ namespace canvas
|
||||
};
|
||||
|
||||
Type type;
|
||||
ElementWeakPtr target;
|
||||
ElementWeakPtr target,
|
||||
current_target;
|
||||
double time;
|
||||
bool propagation_stopped;
|
||||
|
||||
@@ -55,6 +56,7 @@ namespace canvas
|
||||
std::string getTypeString() const;
|
||||
|
||||
ElementWeakPtr getTarget() const;
|
||||
ElementWeakPtr getCurrentTarget() const;
|
||||
|
||||
double getTime() const;
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// Listener for canvas (GUI) events being passed to a Nasal function/code
|
||||
//
|
||||
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "CanvasEvent.hxx"
|
||||
#include "CanvasEventListener.hxx"
|
||||
#include "CanvasSystemAdapter.hxx"
|
||||
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace canvas
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
EventListener::EventListener(naRef code, const SystemAdapterPtr& sys_adapter):
|
||||
_code(code),
|
||||
_gc_key(-1),
|
||||
_sys(sys_adapter)
|
||||
{
|
||||
assert( sys_adapter );
|
||||
if( !naIsCode(code)
|
||||
&& !naIsCCode(code)
|
||||
&& !naIsFunc(code) )
|
||||
throw std::runtime_error
|
||||
(
|
||||
"canvas::EventListener: invalid function argument"
|
||||
);
|
||||
|
||||
_gc_key = sys_adapter->gcSave(_code);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
EventListener::~EventListener()
|
||||
{
|
||||
assert( !_sys.expired() );
|
||||
_sys.lock()->gcRelease(_gc_key);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void EventListener::call(const canvas::EventPtr& event)
|
||||
{
|
||||
SystemAdapterPtr sys = _sys.lock();
|
||||
|
||||
naRef args[] = {
|
||||
nasal::Ghost<EventPtr>::create(sys->getNasalContext(), event)
|
||||
};
|
||||
const int num_args = sizeof(args)/sizeof(args[0]);
|
||||
|
||||
sys->callMethod(_code, naNil(), num_args, args, naNil());
|
||||
}
|
||||
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace simgear
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "CanvasEventManager.hxx"
|
||||
#include "MouseEvent.hxx"
|
||||
#include <simgear/canvas/elements/CanvasElement.hxx>
|
||||
#include <cmath>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -71,6 +72,7 @@ namespace canvas
|
||||
bool EventManager::handleEvent( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path )
|
||||
{
|
||||
bool handled = false;
|
||||
switch( event->type )
|
||||
{
|
||||
case Event::MOUSE_DOWN:
|
||||
@@ -78,20 +80,27 @@ namespace canvas
|
||||
break;
|
||||
case Event::MOUSE_UP:
|
||||
{
|
||||
if( _last_mouse_down.path.empty() )
|
||||
// Ignore mouse up without any previous mouse down
|
||||
return false;
|
||||
// If the mouse has moved while a button was down (aka. dragging) we
|
||||
// need to notify the original element that the mouse has left it, and
|
||||
// the new element that it has been entered
|
||||
if( _last_mouse_down.path != path )
|
||||
handled |= handleMove(event, path);
|
||||
|
||||
// normal mouseup
|
||||
propagateEvent(event, path);
|
||||
handled |= propagateEvent(event, path);
|
||||
|
||||
if( _last_mouse_down.path.empty() )
|
||||
// Ignore mouse up without any previous mouse down
|
||||
return handled;
|
||||
|
||||
// now handle click/dblclick
|
||||
if( checkClickDistance(path, _last_mouse_down.path) )
|
||||
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
|
||||
handled |=
|
||||
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
|
||||
|
||||
_last_mouse_down.clear();
|
||||
|
||||
return true;
|
||||
return handled;
|
||||
}
|
||||
case Event::DRAG:
|
||||
if( !_last_mouse_down.valid() )
|
||||
@@ -99,11 +108,18 @@ namespace canvas
|
||||
else
|
||||
return propagateEvent(event, _last_mouse_down.path);
|
||||
case Event::MOUSE_MOVE:
|
||||
handleMove(event, path);
|
||||
handled |= handleMove(event, path);
|
||||
break;
|
||||
case Event::MOUSE_LEAVE:
|
||||
// Mouse leaves window and therefore also current mouseover element
|
||||
handleMove(event, EventPropagationPath());
|
||||
|
||||
// Event is only send if mouse is moved outside the window or dragging
|
||||
// has ended somewhere outside the window. In both cases a mouse button
|
||||
// has been released, so no more mouse down or click...
|
||||
_last_mouse_down.clear();
|
||||
_last_click.clear();
|
||||
|
||||
return true;
|
||||
case Event::WHEEL:
|
||||
break;
|
||||
@@ -111,11 +127,11 @@ namespace canvas
|
||||
return false;
|
||||
}
|
||||
|
||||
return propagateEvent(event, path);
|
||||
return handled | propagateEvent(event, path);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void EventManager::handleClick( const MouseEventPtr& event,
|
||||
bool EventManager::handleClick( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path )
|
||||
{
|
||||
MouseEventPtr click(new MouseEvent(*event));
|
||||
@@ -145,28 +161,33 @@ namespace canvas
|
||||
dbl_click->type = Event::DBL_CLICK;
|
||||
}
|
||||
|
||||
propagateEvent(click, path);
|
||||
bool handled = propagateEvent(click, path);
|
||||
|
||||
if( dbl_click )
|
||||
propagateEvent(dbl_click, getCommonAncestor(_last_click.path, path));
|
||||
handled |= propagateEvent( dbl_click,
|
||||
getCommonAncestor(_last_click.path, path) );
|
||||
|
||||
_last_click = StampedPropagationPath(path, event->getTime());
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void EventManager::handleMove( const MouseEventPtr& event,
|
||||
bool EventManager::handleMove( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path )
|
||||
{
|
||||
EventPropagationPath& last_path = _last_mouse_over.path;
|
||||
if( last_path == path )
|
||||
return;
|
||||
return false;
|
||||
|
||||
bool handled = false;
|
||||
|
||||
// Leave old element
|
||||
if( !last_path.empty() )
|
||||
{
|
||||
MouseEventPtr mouseout(new MouseEvent(*event));
|
||||
mouseout->type = Event::MOUSE_OUT;
|
||||
propagateEvent(mouseout, last_path);
|
||||
handled |= propagateEvent(mouseout, last_path);
|
||||
|
||||
// Send a mouseleave event to all ancestors of the currently left element
|
||||
// which are not ancestor of the new element currently entered
|
||||
@@ -178,7 +199,7 @@ namespace canvas
|
||||
|
||||
MouseEventPtr mouseleave(new MouseEvent(*event));
|
||||
mouseleave->type = Event::MOUSE_LEAVE;
|
||||
propagateEvent(mouseleave, path_leave);
|
||||
handled |= propagateEvent(mouseleave, path_leave);
|
||||
|
||||
path_leave.pop_back();
|
||||
}
|
||||
@@ -189,7 +210,7 @@ namespace canvas
|
||||
{
|
||||
MouseEventPtr mouseover(new MouseEvent(*event));
|
||||
mouseover->type = Event::MOUSE_OVER;
|
||||
propagateEvent(mouseover, path);
|
||||
handled |= propagateEvent(mouseover, path);
|
||||
|
||||
// Send a mouseenter event to all ancestors of the currently entered
|
||||
// element which are not ancestor of the old element currently being
|
||||
@@ -204,11 +225,12 @@ namespace canvas
|
||||
|
||||
MouseEventPtr mouseenter(new MouseEvent(*event));
|
||||
mouseenter->type = Event::MOUSE_ENTER;
|
||||
propagateEvent(mouseenter, path_enter);
|
||||
handled |= propagateEvent(mouseenter, path_enter);
|
||||
}
|
||||
}
|
||||
|
||||
_last_mouse_over.path = path;
|
||||
return handled;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -230,6 +252,21 @@ namespace canvas
|
||||
// std::cout << it->element.lock()->getProps()->getPath() << std::endl;
|
||||
// }
|
||||
|
||||
// Check if event supports bubbling
|
||||
const Event::Type types_no_bubbling[] = {
|
||||
Event::MOUSE_ENTER,
|
||||
Event::MOUSE_LEAVE,
|
||||
};
|
||||
const size_t num_types_no_bubbling = sizeof(types_no_bubbling)
|
||||
/ sizeof(types_no_bubbling[0]);
|
||||
bool do_bubble = true;
|
||||
for( size_t i = 0; i < num_types_no_bubbling; ++i )
|
||||
if( event->type == types_no_bubbling[i] )
|
||||
{
|
||||
do_bubble = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Bubbling phase
|
||||
for( EventPropagationPath::const_reverse_iterator
|
||||
it = path.rbegin();
|
||||
@@ -239,9 +276,14 @@ namespace canvas
|
||||
ElementPtr el = it->element.lock();
|
||||
|
||||
if( !el )
|
||||
{
|
||||
// Ignore element if it has been destroyed while traversing the event
|
||||
// (eg. removed by another event handler)
|
||||
continue;
|
||||
if( do_bubble )
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO provide functions to convert delta to local coordinates on demand.
|
||||
// Maybe also provide a clone method for events as local coordinates
|
||||
@@ -258,9 +300,10 @@ namespace canvas
|
||||
//mouse_event->delta = it->local_delta;
|
||||
}
|
||||
|
||||
event->current_target = el;
|
||||
el->handleEvent(event);
|
||||
|
||||
if( event->propagation_stopped )
|
||||
if( event->propagation_stopped || !do_bubble )
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -273,8 +316,8 @@ namespace canvas
|
||||
const EventPropagationPath& path2 ) const
|
||||
{
|
||||
osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
|
||||
return delta.x() < drag_threshold
|
||||
&& delta.y() < drag_threshold;
|
||||
return std::fabs(delta.x()) < drag_threshold
|
||||
&& std::fabs(delta.y()) < drag_threshold;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -70,13 +70,13 @@ namespace canvas
|
||||
/**
|
||||
* Propagate click event and handle multi-click (eg. create dblclick)
|
||||
*/
|
||||
void handleClick( const MouseEventPtr& event,
|
||||
bool handleClick( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path );
|
||||
|
||||
/**
|
||||
* Handle mouseover/enter/out/leave
|
||||
*/
|
||||
void handleMove( const MouseEventPtr& event,
|
||||
bool handleMove( const MouseEventPtr& event,
|
||||
const EventPropagationPath& path );
|
||||
|
||||
bool propagateEvent( const EventPtr& event,
|
||||
|
||||
@@ -30,8 +30,10 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
EventVisitor::EventVisitor( TraverseMode mode,
|
||||
const osg::Vec2f& pos,
|
||||
const osg::Vec2f& delta ):
|
||||
_traverse_mode( mode )
|
||||
const osg::Vec2f& delta,
|
||||
const ElementPtr& root ):
|
||||
_traverse_mode( mode ),
|
||||
_root(root)
|
||||
{
|
||||
if( mode == TRAVERSE_DOWN )
|
||||
{
|
||||
@@ -70,10 +72,11 @@ namespace canvas
|
||||
m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
|
||||
);
|
||||
|
||||
// Don't check collision with root element (2nd element in _target_path)
|
||||
// do event listeners attached to the canvas itself (its root group)
|
||||
// always get called even if no element has been hit.
|
||||
if( _target_path.size() > 1 && !el.hitBound(pos, local_pos) )
|
||||
// Don't check specified root element for collision, as its purpose is to
|
||||
// catch all events which have no target. This allows for example calling
|
||||
// event listeners attached to the canvas itself (its root group) even if
|
||||
// no element has been hit.
|
||||
if( _root.get() != &el && !el.hitBound(pos, local_pos) )
|
||||
return false;
|
||||
|
||||
const osg::Vec2f& delta = _target_path.back().local_delta;
|
||||
@@ -86,7 +89,7 @@ namespace canvas
|
||||
EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
|
||||
_target_path.push_back(target);
|
||||
|
||||
if( el.traverse(*this) || _target_path.size() <= 2 )
|
||||
if( el.traverse(*this) || &el == _root.get() )
|
||||
return true;
|
||||
|
||||
_target_path.pop_back();
|
||||
|
||||
@@ -38,9 +38,17 @@ namespace canvas
|
||||
TRAVERSE_DOWN
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mode
|
||||
* @param pos Mouse position
|
||||
* @param delta Mouse movement since last mouse move event
|
||||
* @param root Element to dispatch events to if no element is hit
|
||||
*/
|
||||
EventVisitor( TraverseMode mode,
|
||||
const osg::Vec2f& pos,
|
||||
const osg::Vec2f& delta );
|
||||
const osg::Vec2f& delta,
|
||||
const ElementPtr& root = ElementPtr() );
|
||||
virtual ~EventVisitor();
|
||||
virtual bool traverse(Element& el);
|
||||
virtual bool apply(Element& el);
|
||||
@@ -51,6 +59,7 @@ namespace canvas
|
||||
|
||||
TraverseMode _traverse_mode;
|
||||
EventPropagationPath _target_path;
|
||||
ElementPtr _root;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "CanvasMgr.hxx"
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasEventManager.hxx"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
@@ -35,10 +36,8 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
CanvasMgr::CanvasMgr( SGPropertyNode_ptr node,
|
||||
SystemAdapterPtr system_adapter ):
|
||||
PropertyBasedMgr(node, "texture", &canvasFactory),
|
||||
_system_adapter(system_adapter)
|
||||
CanvasMgr::CanvasMgr(SGPropertyNode_ptr node):
|
||||
PropertyBasedMgr(node, "texture", &canvasFactory)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -65,7 +64,6 @@ namespace canvas
|
||||
void CanvasMgr::elementCreated(PropertyBasedElementPtr element)
|
||||
{
|
||||
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element);
|
||||
canvas->setSystemAdapter(_system_adapter);
|
||||
canvas->setCanvasMgr(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,8 @@ namespace canvas
|
||||
|
||||
/**
|
||||
* @param node Root node of branch used to control canvasses
|
||||
* @param system_adapter Adapter for connecting between canvas and
|
||||
* application framework
|
||||
*
|
||||
*/
|
||||
CanvasMgr( SGPropertyNode_ptr node,
|
||||
SystemAdapterPtr system_adapter );
|
||||
CanvasMgr(SGPropertyNode_ptr node);
|
||||
|
||||
/**
|
||||
* Create a new canvas
|
||||
@@ -65,8 +61,6 @@ namespace canvas
|
||||
|
||||
protected:
|
||||
|
||||
SystemAdapterPtr _system_adapter;
|
||||
|
||||
virtual void elementCreated(PropertyBasedElementPtr element);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#define SG_CANVAS_SYSTEM_ADAPTER_HXX_
|
||||
|
||||
#include "canvas_fwd.hxx"
|
||||
#include <simgear/nasal/nasal.h>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -36,29 +35,6 @@ namespace canvas
|
||||
virtual void addCamera(osg::Camera* camera) const = 0;
|
||||
virtual void removeCamera(osg::Camera* camera) const = 0;
|
||||
virtual osg::Image* getImage(const std::string& path) const = 0;
|
||||
|
||||
virtual naContext getNasalContext() const = 0;
|
||||
|
||||
/**
|
||||
* Save passed reference to Nasal object from being deleted by the
|
||||
* garbage collector.
|
||||
*/
|
||||
virtual int gcSave(naRef r) = 0;
|
||||
|
||||
/**
|
||||
* Release an object previously passed to ::gcSave to allow it being
|
||||
* cleaned up by the garbage collector.
|
||||
*/
|
||||
virtual void gcRelease(int key) = 0;
|
||||
|
||||
/**
|
||||
* Call a Nasal function with the given environment and arguments.
|
||||
*/
|
||||
virtual naRef callMethod( naRef code,
|
||||
naRef self,
|
||||
int argc,
|
||||
naRef* args,
|
||||
naRef locals ) = 0;
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#endif
|
||||
|
||||
#include "ODGauge.hxx"
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasSystemAdapter.hxx"
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
@@ -43,6 +44,7 @@
|
||||
#include <osg/ShadeModel>
|
||||
#include <osg/StateSet>
|
||||
#include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
|
||||
#include <osgUtil/RenderBin>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -51,6 +53,58 @@ namespace simgear
|
||||
namespace canvas
|
||||
{
|
||||
|
||||
class PreOrderBin:
|
||||
public osgUtil::RenderBin
|
||||
{
|
||||
public:
|
||||
|
||||
PreOrderBin()
|
||||
{}
|
||||
PreOrderBin( const RenderBin& rhs,
|
||||
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY ):
|
||||
RenderBin(rhs, copyop)
|
||||
{}
|
||||
|
||||
virtual osg::Object* cloneType() const
|
||||
{
|
||||
return new PreOrderBin();
|
||||
}
|
||||
virtual osg::Object* clone(const osg::CopyOp& copyop) const
|
||||
{
|
||||
return new PreOrderBin(*this,copyop);
|
||||
}
|
||||
virtual bool isSameKindAs(const osg::Object* obj) const
|
||||
{
|
||||
return dynamic_cast<const PreOrderBin*>(obj) != 0L;
|
||||
}
|
||||
virtual const char* className() const
|
||||
{
|
||||
return "PreOrderBin";
|
||||
}
|
||||
|
||||
virtual void sort()
|
||||
{
|
||||
// Do not sort to keep traversal order...
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef OSG_INIT_SINGLETON_PROXY
|
||||
/**
|
||||
* http://svn.openscenegraph.org/osg/OpenSceneGraph/trunk/include/osg/Object
|
||||
*
|
||||
* Helper macro that creates a static proxy object to call singleton function
|
||||
* on it's construction, ensuring that the singleton gets initialized at
|
||||
* startup.
|
||||
*/
|
||||
# define OSG_INIT_SINGLETON_PROXY(ProxyName, Func)\
|
||||
static struct ProxyName{ ProxyName() { Func; } } s_##ProxyName;
|
||||
#endif
|
||||
|
||||
OSG_INIT_SINGLETON_PROXY(
|
||||
PreOrderBinProxy,
|
||||
(osgUtil::RenderBin::addRenderBinPrototype("PreOrderBin", new PreOrderBin))
|
||||
);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ODGauge::ODGauge():
|
||||
_size_x( -1 ),
|
||||
@@ -70,12 +124,6 @@ namespace canvas
|
||||
clear();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setSystemAdapter(const SystemAdapterPtr& system_adapter)
|
||||
{
|
||||
_system_adapter = system_adapter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setSize(int size_x, int size_y)
|
||||
{
|
||||
@@ -209,8 +257,8 @@ namespace canvas
|
||||
updateSampling();
|
||||
updateBlendMode();
|
||||
|
||||
if( _system_adapter )
|
||||
_system_adapter->addCamera(camera.get());
|
||||
if( Canvas::getSystemAdapter() )
|
||||
Canvas::getSystemAdapter()->addCamera(camera.get());
|
||||
|
||||
_flags |= AVAILABLE;
|
||||
}
|
||||
@@ -226,8 +274,8 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::clear()
|
||||
{
|
||||
if( camera.valid() && _system_adapter )
|
||||
_system_adapter->removeCamera(camera.get());
|
||||
if( camera.valid() && Canvas::getSystemAdapter() )
|
||||
Canvas::getSystemAdapter()->removeCamera(camera.get());
|
||||
camera.release();
|
||||
texture.release();
|
||||
|
||||
|
||||
@@ -53,8 +53,6 @@ namespace canvas
|
||||
ODGauge();
|
||||
virtual ~ODGauge();
|
||||
|
||||
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
|
||||
|
||||
/**
|
||||
* Set the size of the render target.
|
||||
*
|
||||
@@ -136,8 +134,6 @@ namespace canvas
|
||||
|
||||
protected:
|
||||
|
||||
SystemAdapterPtr _system_adapter;
|
||||
|
||||
int _size_x,
|
||||
_size_y,
|
||||
_view_width,
|
||||
|
||||
@@ -51,7 +51,6 @@ namespace canvas
|
||||
SG_FWD_DECL(Text)
|
||||
|
||||
SG_FWD_DECL(Event)
|
||||
SG_FWD_DECL(EventListener)
|
||||
SG_FWD_DECL(MouseEvent)
|
||||
SG_FWD_DECL(Placement)
|
||||
SG_FWD_DECL(SystemAdapter)
|
||||
@@ -73,6 +72,8 @@ namespace canvas
|
||||
typedef boost::function<Placements( SGPropertyNode*,
|
||||
CanvasPtr )> PlacementFactory;
|
||||
|
||||
typedef boost::function<void(const EventPtr&)> EventListener;
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace simgear
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "CanvasElement.hxx"
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
#include <simgear/canvas/CanvasEventListener.hxx>
|
||||
#include <simgear/canvas/CanvasEventVisitor.hxx>
|
||||
#include <simgear/canvas/MouseEvent.hxx>
|
||||
#include <simgear/math/SGMisc.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/scene/material/parseBlendFunc.hxx>
|
||||
|
||||
#include <osg/Drawable>
|
||||
@@ -29,11 +29,10 @@
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace simgear
|
||||
@@ -42,6 +41,63 @@ namespace canvas
|
||||
{
|
||||
const std::string NAME_TRANSFORM = "tf";
|
||||
|
||||
/**
|
||||
* glScissor with coordinates relative to different reference frames.
|
||||
*/
|
||||
class Element::RelativeScissor:
|
||||
public osg::Scissor
|
||||
{
|
||||
public:
|
||||
|
||||
ReferenceFrame _coord_reference;
|
||||
osg::Matrix _parent_inverse;
|
||||
|
||||
RelativeScissor():
|
||||
_coord_reference(GLOBAL)
|
||||
{}
|
||||
|
||||
virtual void apply(osg::State& state) const
|
||||
{
|
||||
const osg::Viewport* vp = state.getCurrentViewport();
|
||||
float w2 = 0.5 * vp->width(),
|
||||
h2 = 0.5 * vp->height();
|
||||
|
||||
osg::Matrix model_view
|
||||
(
|
||||
w2, 0, 0, 0,
|
||||
0, h2, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
w2, h2, 0, 1
|
||||
);
|
||||
model_view.preMult(state.getProjectionMatrix());
|
||||
|
||||
if( _coord_reference != GLOBAL )
|
||||
{
|
||||
model_view.preMult(state.getModelViewMatrix());
|
||||
|
||||
if( _coord_reference == PARENT )
|
||||
model_view.preMult(_parent_inverse);
|
||||
}
|
||||
|
||||
const osg::Vec2 scale( model_view(0,0), model_view(1,1)),
|
||||
offset(model_view(3,0), model_view(3,1));
|
||||
|
||||
// TODO check/warn for rotation?
|
||||
|
||||
GLint x = SGMiscf::roundToInt(scale.x() * _x + offset.x()),
|
||||
y = SGMiscf::roundToInt(scale.y() * _y + offset.y()),
|
||||
w = SGMiscf::roundToInt(std::fabs(scale.x()) * _width),
|
||||
h = SGMiscf::roundToInt(std::fabs(scale.y()) * _height);
|
||||
|
||||
if( scale.x() < 0 )
|
||||
x -= w;
|
||||
if( scale.y() < 0 )
|
||||
y -= h;
|
||||
|
||||
glScissor(x, y, w, h);
|
||||
}
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Element::OSGUserData::OSGUserData(ElementPtr element):
|
||||
element(element)
|
||||
@@ -52,7 +108,19 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
Element::~Element()
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
return;
|
||||
|
||||
for(unsigned int i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
OSGUserData* ud =
|
||||
static_cast<OSGUserData*>(_transform->getChild(i)->getUserData());
|
||||
|
||||
if( ud )
|
||||
// Ensure parent is cleared to prevent accessing released memory if an
|
||||
// element somehow survives longer than his parent.
|
||||
ud->element->_parent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -86,6 +154,12 @@ namespace canvas
|
||||
return boost::static_pointer_cast<Element>(_self.lock());
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
ElementPtr Element::getParent()
|
||||
{
|
||||
return _parent ? _parent->getWeakPtr().lock() : ElementPtr();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::update(double dt)
|
||||
{
|
||||
@@ -151,6 +225,15 @@ namespace canvas
|
||||
}
|
||||
_transform->setMatrix(m);
|
||||
_transform_dirty = false;
|
||||
_attributes_dirty |= SCISSOR_COORDS;
|
||||
}
|
||||
|
||||
if( _attributes_dirty & SCISSOR_COORDS )
|
||||
{
|
||||
if( _scissor && _scissor->_coord_reference != GLOBAL )
|
||||
_scissor->_parent_inverse = _transform->getInverseMatrix();
|
||||
|
||||
_attributes_dirty &= ~SCISSOR_COORDS;
|
||||
}
|
||||
|
||||
// Update bounding box on manual update (manual updates pass zero dt)
|
||||
@@ -173,31 +256,28 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
naRef Element::addEventListener(const nasal::CallContext& ctx)
|
||||
bool Element::addEventListener( const std::string& type_str,
|
||||
const EventListener& cb )
|
||||
{
|
||||
const std::string type_str = ctx.requireArg<std::string>(0);
|
||||
naRef code = ctx.requireArg<naRef>(1);
|
||||
|
||||
SG_LOG
|
||||
(
|
||||
SG_NASAL,
|
||||
SG_GENERAL,
|
||||
SG_INFO,
|
||||
"addEventListener(" << _node->getPath() << ", " << type_str << ")"
|
||||
);
|
||||
|
||||
Event::Type type = Event::strToType(type_str);
|
||||
if( type == Event::UNKNOWN )
|
||||
naRuntimeError( ctx.c,
|
||||
"addEventListener: Unknown event type %s",
|
||||
type_str.c_str() );
|
||||
{
|
||||
SG_LOG( SG_GENERAL,
|
||||
SG_WARN,
|
||||
"addEventListener: Unknown event type " << type_str );
|
||||
return false;
|
||||
}
|
||||
|
||||
_listener[ type ].push_back
|
||||
(
|
||||
boost::make_shared<EventListener>( code,
|
||||
_canvas.lock()->getSystemAdapter() )
|
||||
);
|
||||
_listener[ type ].push_back(cb);
|
||||
|
||||
return naNil();
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -209,7 +289,7 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::accept(EventVisitor& visitor)
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
if( !isVisible() )
|
||||
return false;
|
||||
|
||||
return visitor.apply(*this);
|
||||
@@ -236,8 +316,8 @@ namespace canvas
|
||||
if( listeners == _listener.end() )
|
||||
return false;
|
||||
|
||||
BOOST_FOREACH(EventListenerPtr listener, listeners->second)
|
||||
listener->call(event);
|
||||
BOOST_FOREACH(EventListener const& listener, listeners->second)
|
||||
listener(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -259,7 +339,7 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::isVisible() const
|
||||
{
|
||||
return _transform->getNodeMask() != 0;
|
||||
return _transform.valid() && _transform->getNodeMask() != 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -315,29 +395,37 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
|
||||
{
|
||||
if( parent == _node && child->getNameString() == NAME_TRANSFORM )
|
||||
if( parent == _node )
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
return;
|
||||
|
||||
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
|
||||
if( child->getNameString() == NAME_TRANSFORM )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Element::childRemoved: unknown transform: " << child->getPath()
|
||||
);
|
||||
if( !_transform.valid() )
|
||||
return;
|
||||
|
||||
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_GENERAL,
|
||||
SG_WARN,
|
||||
"Element::childRemoved: unknown transform: " << child->getPath()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_transform_types[ child->getIndex() ] = TT_NONE;
|
||||
|
||||
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
|
||||
_transform_types.pop_back();
|
||||
|
||||
_transform_dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_transform_types[ child->getIndex() ] = TT_NONE;
|
||||
|
||||
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
|
||||
_transform_types.pop_back();
|
||||
|
||||
_transform_dirty = true;
|
||||
return;
|
||||
else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
|
||||
{
|
||||
if( setStyle(getParentStyle(child), style) )
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
childRemoved(child);
|
||||
@@ -350,8 +438,17 @@ namespace canvas
|
||||
if( parent == _node )
|
||||
{
|
||||
const std::string& name = child->getNameString();
|
||||
if( setStyle(child) )
|
||||
if( StyleInfo const* style_info = getStyleInfo(name) )
|
||||
{
|
||||
SGPropertyNode const* style = child;
|
||||
if( isStyleEmpty(child) )
|
||||
{
|
||||
child->clearValue();
|
||||
style = getParentStyle(child);
|
||||
}
|
||||
setStyle(style, style_info);
|
||||
return;
|
||||
}
|
||||
else if( name == "update" )
|
||||
return update(0);
|
||||
else if( name == "visible" )
|
||||
@@ -371,21 +468,10 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::setStyle(const SGPropertyNode* child)
|
||||
bool Element::setStyle( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info )
|
||||
{
|
||||
StyleSetters::const_iterator setter =
|
||||
_style_setters.find(child->getNameString());
|
||||
if( setter == _style_setters.end() )
|
||||
return false;
|
||||
|
||||
const StyleSetter* style_setter = &setter->second.setter;
|
||||
while( style_setter )
|
||||
{
|
||||
if( style_setter->func(*this, child) )
|
||||
return true;
|
||||
style_setter = style_setter->next;
|
||||
}
|
||||
return false;
|
||||
return canApplyStyle(child) && setStyleImpl(child, style_info);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -394,6 +480,7 @@ namespace canvas
|
||||
if( clip.empty() || clip == "auto" )
|
||||
{
|
||||
getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
|
||||
_scissor = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -406,17 +493,22 @@ namespace canvas
|
||||
return;
|
||||
}
|
||||
|
||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
||||
const boost::char_separator<char> del(", \t\npx");
|
||||
|
||||
tokenizer tokens(clip.begin() + RECT.size(), clip.end() - 1, del);
|
||||
const std::string sep(", \t\npx");
|
||||
int comp = 0;
|
||||
int values[4];
|
||||
for( tokenizer::const_iterator tok = tokens.begin();
|
||||
tok != tokens.end() && comp < 4;
|
||||
++tok, ++comp )
|
||||
float values[4];
|
||||
|
||||
for(size_t pos = RECT.size(); comp < 4; ++comp)
|
||||
{
|
||||
values[comp] = boost::lexical_cast<int>(*tok);
|
||||
pos = clip.find_first_not_of(sep, pos);
|
||||
if( pos == std::string::npos || pos == clip.size() - 1 )
|
||||
break;
|
||||
|
||||
char *end = 0;
|
||||
values[comp] = strtod(&clip[pos], &end);
|
||||
if( end == &clip[pos] || !end )
|
||||
break;
|
||||
|
||||
pos = end - &clip[0];
|
||||
}
|
||||
|
||||
if( comp < 4 )
|
||||
@@ -425,32 +517,37 @@ namespace canvas
|
||||
return;
|
||||
}
|
||||
|
||||
float scale_x = 1,
|
||||
scale_y = 1;
|
||||
float width = values[1] - values[3],
|
||||
height = values[2] - values[0];
|
||||
|
||||
CanvasPtr canvas = _canvas.lock();
|
||||
if( canvas )
|
||||
if( width < 0 || height < 0 )
|
||||
{
|
||||
// The scissor rectangle isn't affected by any transformation, so we need
|
||||
// to convert to image/canvas coordinates on our selves.
|
||||
scale_x = canvas->getSizeX()
|
||||
/ static_cast<float>(canvas->getViewWidth());
|
||||
scale_y = canvas->getSizeY()
|
||||
/ static_cast<float>(canvas->getViewHeight());
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
|
||||
return;
|
||||
}
|
||||
|
||||
osg::Scissor* scissor = new osg::Scissor();
|
||||
_scissor = new RelativeScissor();
|
||||
// <top>, <right>, <bottom>, <left>
|
||||
scissor->x() = scale_x * values[3];
|
||||
scissor->y() = scale_y * values[0];
|
||||
scissor->width() = scale_x * (values[1] - values[3]);
|
||||
scissor->height() = scale_y * (values[2] - values[0]);
|
||||
_scissor->x() = SGMiscf::roundToInt(values[3]);
|
||||
_scissor->y() = SGMiscf::roundToInt(values[0]);
|
||||
_scissor->width() = SGMiscf::roundToInt(width);
|
||||
_scissor->height() = SGMiscf::roundToInt(height);
|
||||
|
||||
if( canvas )
|
||||
// Canvas has y axis upside down
|
||||
scissor->y() = canvas->getSizeY() - scissor->y() - scissor->height();
|
||||
getOrCreateStateSet()->setAttributeAndModes(_scissor);
|
||||
|
||||
getOrCreateStateSet()->setAttributeAndModes(scissor);
|
||||
SGPropertyNode* clip_frame = _node->getChild("clip-frame", 0);
|
||||
if( clip_frame )
|
||||
valueChanged(clip_frame);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::setClipFrame(ReferenceFrame rf)
|
||||
{
|
||||
if( _scissor )
|
||||
{
|
||||
_scissor->_coord_reference = rf;
|
||||
_attributes_dirty |= SCISSOR_COORDS;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -481,7 +578,7 @@ namespace canvas
|
||||
osg::BoundingBox transformed;
|
||||
const osg::BoundingBox& bb = _drawable->getBound();
|
||||
for(int i = 0; i < 4; ++i)
|
||||
transformed.expandBy( m * bb.corner(i) );
|
||||
transformed.expandBy( bb.corner(i) * m );
|
||||
|
||||
return transformed;
|
||||
}
|
||||
@@ -501,8 +598,11 @@ namespace canvas
|
||||
_transform_dirty( false ),
|
||||
_transform( new osg::MatrixTransform ),
|
||||
_style( parent_style ),
|
||||
_scissor( 0 ),
|
||||
_drawable( 0 )
|
||||
{
|
||||
staticInit();
|
||||
|
||||
SG_LOG
|
||||
(
|
||||
SG_GL,
|
||||
@@ -510,10 +610,93 @@ namespace canvas
|
||||
"New canvas element " << node->getPath()
|
||||
);
|
||||
|
||||
if( !isInit<Element>() )
|
||||
// Ensure elements are drawn in order they appear in the element tree
|
||||
_transform->getOrCreateStateSet()
|
||||
->setRenderBinDetails
|
||||
(
|
||||
0,
|
||||
"PreOrderBin",
|
||||
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
|
||||
);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Element::staticInit()
|
||||
{
|
||||
if( isInit<Element>() )
|
||||
return;
|
||||
|
||||
addStyle("clip", "", &Element::setClip, false);
|
||||
addStyle("clip-frame", "", &Element::setClipFrame, false);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::isStyleEmpty(const SGPropertyNode* child) const
|
||||
{
|
||||
return !child
|
||||
|| simgear::strutils::strip(child->getStringValue()).empty();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::canApplyStyle(const SGPropertyNode* child) const
|
||||
{
|
||||
if( _node == child->getParent() )
|
||||
return true;
|
||||
|
||||
// Parent values do not override if element has own value
|
||||
return isStyleEmpty( _node->getChild(child->getName()) );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Element::setStyleImpl( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info )
|
||||
{
|
||||
const StyleSetter* style_setter = style_info
|
||||
? &style_info->setter
|
||||
: getStyleSetter(child->getNameString());
|
||||
while( style_setter )
|
||||
{
|
||||
addStyle("clip", "", &Element::setClip);
|
||||
if( style_setter->func(*this, child) )
|
||||
return true;
|
||||
style_setter = style_setter->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const Element::StyleInfo*
|
||||
Element::getStyleInfo(const std::string& name) const
|
||||
{
|
||||
StyleSetters::const_iterator setter = _style_setters.find(name);
|
||||
if( setter == _style_setters.end() )
|
||||
return 0;
|
||||
|
||||
return &setter->second;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const Element::StyleSetter*
|
||||
Element::getStyleSetter(const std::string& name) const
|
||||
{
|
||||
const StyleInfo* info = getStyleInfo(name);
|
||||
return info ? &info->setter : 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const SGPropertyNode*
|
||||
Element::getParentStyle(const SGPropertyNode* child) const
|
||||
{
|
||||
// Try to get value from parent...
|
||||
if( _parent )
|
||||
{
|
||||
Style::const_iterator style =
|
||||
_parent->_style.find(child->getNameString());
|
||||
if( style != _parent->_style.end() )
|
||||
return style->second;
|
||||
}
|
||||
|
||||
// ...or reset to default if none is available
|
||||
return child; // TODO somehow get default value for each style?
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
#include <simgear/canvas/CanvasEvent.hxx>
|
||||
#include <simgear/props/PropertyBasedElement.hxx>
|
||||
#include <simgear/misc/stdint.hxx> // for uint32_t
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
|
||||
#include <osg/BoundingBox>
|
||||
#include <osg/MatrixTransform>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
@@ -71,6 +71,19 @@ namespace canvas
|
||||
{
|
||||
StyleSetter setter; ///!< Function(s) for setting this style
|
||||
std::string type; ///!< Interpolation type
|
||||
bool inheritable; ///!< Whether children can inherit this style from
|
||||
/// their parents
|
||||
};
|
||||
|
||||
/**
|
||||
* Coordinate reference frame (eg. "clip" property)
|
||||
*/
|
||||
enum ReferenceFrame
|
||||
{
|
||||
GLOBAL, ///!< Global coordinates
|
||||
PARENT, ///!< Coordinates relative to parent coordinate frame
|
||||
LOCAL ///!< Coordinates relative to local coordinates (parent
|
||||
/// coordinates with local transformations applied)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -82,6 +95,7 @@ namespace canvas
|
||||
virtual void onDestroy();
|
||||
|
||||
ElementWeakPtr getWeakPtr() const;
|
||||
ElementPtr getParent();
|
||||
|
||||
/**
|
||||
* Called every frame to update internal state
|
||||
@@ -90,7 +104,7 @@ namespace canvas
|
||||
*/
|
||||
virtual void update(double dt);
|
||||
|
||||
naRef addEventListener(const nasal::CallContext& ctx);
|
||||
bool addEventListener(const std::string& type, const EventListener& cb);
|
||||
virtual void clearEventListener();
|
||||
|
||||
virtual bool accept(EventVisitor& visitor);
|
||||
@@ -117,7 +131,8 @@ namespace canvas
|
||||
SGPropertyNode * child );
|
||||
virtual void valueChanged(SGPropertyNode * child);
|
||||
|
||||
virtual bool setStyle(const SGPropertyNode* child);
|
||||
virtual bool setStyle( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info = 0 );
|
||||
|
||||
/**
|
||||
* Set clipping shape
|
||||
@@ -127,6 +142,11 @@ namespace canvas
|
||||
*/
|
||||
void setClip(const std::string& clip);
|
||||
|
||||
/**
|
||||
* Clipping coordinates reference frame
|
||||
*/
|
||||
void setClipFrame(ReferenceFrame rf);
|
||||
|
||||
/**
|
||||
* Write the given bounding box to the property tree
|
||||
*/
|
||||
@@ -161,8 +181,9 @@ namespace canvas
|
||||
|
||||
enum Attributes
|
||||
{
|
||||
BLEND_FUNC = 0x0001,
|
||||
LAST_ATTRIBUTE = BLEND_FUNC << 1
|
||||
BLEND_FUNC = 1,
|
||||
SCISSOR_COORDS = BLEND_FUNC << 1,
|
||||
LAST_ATTRIBUTE = SCISSOR_COORDS << 1
|
||||
};
|
||||
|
||||
enum TransformType
|
||||
@@ -174,6 +195,8 @@ namespace canvas
|
||||
TT_SCALE
|
||||
};
|
||||
|
||||
class RelativeScissor;
|
||||
|
||||
CanvasWeakPtr _canvas;
|
||||
Element *_parent;
|
||||
|
||||
@@ -185,8 +208,9 @@ namespace canvas
|
||||
|
||||
Style _style;
|
||||
std::vector<SGPropertyNode_ptr> _bounding_box;
|
||||
RelativeScissor *_scissor;
|
||||
|
||||
typedef std::vector<EventListenerPtr> Listener;
|
||||
typedef std::vector<EventListener> Listener;
|
||||
typedef std::map<Event::Type, Listener> ListenerMap;
|
||||
|
||||
ListenerMap _listener;
|
||||
@@ -194,6 +218,8 @@ namespace canvas
|
||||
typedef std::map<std::string, StyleInfo> StyleSetters;
|
||||
static StyleSetters _style_setters;
|
||||
|
||||
static void staticInit();
|
||||
|
||||
Element( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
const Style& parent_style,
|
||||
@@ -207,7 +233,7 @@ namespace canvas
|
||||
* @tparam Derived (Derived) class type
|
||||
*/
|
||||
template<class Derived>
|
||||
bool isInit() const
|
||||
static bool isInit()
|
||||
{
|
||||
static bool is_init = false;
|
||||
if( is_init )
|
||||
@@ -236,10 +262,12 @@ namespace canvas
|
||||
typename T2,
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
const boost::function<void (Derived&, T2)>& setter )
|
||||
const boost::function<void (Derived&, T2)>& setter,
|
||||
bool inheritable = true )
|
||||
{
|
||||
StyleInfo& style_info = _style_setters[ name ];
|
||||
if( !type.empty() )
|
||||
@@ -255,6 +283,8 @@ namespace canvas
|
||||
|
||||
style_info.type = type;
|
||||
}
|
||||
// TODO check if changed?
|
||||
style_info.inheritable = inheritable;
|
||||
|
||||
StyleSetter* style = &style_info.setter;
|
||||
while( style->next )
|
||||
@@ -276,28 +306,33 @@ namespace canvas
|
||||
typename T,
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
const boost::function<void (Derived&, T)>& setter )
|
||||
const boost::function<void (Derived&, T)>& setter,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T, T>(name, type, setter);
|
||||
return addStyle<T, T>(name, type, setter, inheritable);
|
||||
}
|
||||
|
||||
template<
|
||||
typename T,
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Derived::*setter)(T) )
|
||||
void (Derived::*setter)(T),
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T, T>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Derived&, T)>(setter)
|
||||
boost::function<void (Derived&, T)>(setter),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
@@ -306,32 +341,38 @@ namespace canvas
|
||||
typename T2,
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetterFunc
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Derived::*setter)(T2) )
|
||||
void (Derived::*setter)(T2),
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T1>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Derived&, T2)>(setter)
|
||||
boost::function<void (Derived&, T2)>(setter),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
class Derived
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Derived::*setter)(const std::string&) )
|
||||
void (Derived::*setter)(const std::string&),
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<const char*, const std::string&>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Derived&, const std::string&)>(setter)
|
||||
boost::function<void (Derived&, const std::string&)>(setter),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
@@ -341,13 +382,21 @@ namespace canvas
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Other::*setter)(T),
|
||||
OtherRef Derived::*instance_ref )
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T, T>(name, type, bindOther(setter, instance_ref));
|
||||
return addStyle<T, T>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
bindOther(setter, instance_ref),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
@@ -357,13 +406,21 @@ namespace canvas
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Other::*setter)(T2),
|
||||
OtherRef Derived::*instance_ref )
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T1>(name, type, bindOther(setter, instance_ref));
|
||||
return addStyle<T1>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
bindOther(setter, instance_ref),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
@@ -373,13 +430,21 @@ namespace canvas
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
const boost::function<void (Other&, T2)>& setter,
|
||||
OtherRef Derived::*instance_ref )
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<T1>(name, type, bindOther(setter, instance_ref));
|
||||
return addStyle<T1>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
bindOther(setter, instance_ref),
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<
|
||||
@@ -387,22 +452,26 @@ namespace canvas
|
||||
class Other,
|
||||
class OtherRef
|
||||
>
|
||||
static
|
||||
StyleSetter
|
||||
addStyle( const std::string& name,
|
||||
const std::string& type,
|
||||
void (Other::*setter)(const std::string&),
|
||||
OtherRef Derived::*instance_ref )
|
||||
OtherRef Derived::*instance_ref,
|
||||
bool inheritable = true )
|
||||
{
|
||||
return addStyle<const char*, const std::string&>
|
||||
(
|
||||
name,
|
||||
type,
|
||||
boost::function<void (Other&, const std::string&)>(setter),
|
||||
instance_ref
|
||||
instance_ref,
|
||||
inheritable
|
||||
);
|
||||
}
|
||||
|
||||
template<typename T, class Derived, class Other, class OtherRef>
|
||||
static
|
||||
boost::function<void (Derived&, T)>
|
||||
bindOther( void (Other::*setter)(T), OtherRef Derived::*instance_ref )
|
||||
{
|
||||
@@ -410,6 +479,7 @@ namespace canvas
|
||||
}
|
||||
|
||||
template<typename T, class Derived, class Other, class OtherRef>
|
||||
static
|
||||
boost::function<void (Derived&, T)>
|
||||
bindOther( const boost::function<void (Other&, T)>& setter,
|
||||
OtherRef Derived::*instance_ref )
|
||||
@@ -427,6 +497,7 @@ namespace canvas
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, class Derived>
|
||||
static
|
||||
StyleSetterFuncUnchecked
|
||||
bindStyleSetter( const std::string& name,
|
||||
const boost::function<void (Derived&, T2)>& setter )
|
||||
@@ -441,6 +512,15 @@ namespace canvas
|
||||
);
|
||||
}
|
||||
|
||||
bool isStyleEmpty(const SGPropertyNode* child) const;
|
||||
bool canApplyStyle(const SGPropertyNode* child) const;
|
||||
bool setStyleImpl( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info = 0 );
|
||||
|
||||
const StyleInfo* getStyleInfo(const std::string& name) const;
|
||||
const StyleSetter* getStyleSetter(const std::string& name) const;
|
||||
const SGPropertyNode* getParentStyle(const SGPropertyNode* child) const;
|
||||
|
||||
virtual void childAdded(SGPropertyNode * child) {}
|
||||
virtual void childRemoved(SGPropertyNode * child){}
|
||||
virtual void childChanged(SGPropertyNode * child){}
|
||||
@@ -495,6 +575,27 @@ namespace canvas
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
|
||||
template<>
|
||||
struct enum_traits<canvas::Element::ReferenceFrame>
|
||||
{
|
||||
static const char* name()
|
||||
{
|
||||
return "canvas::Element::ReferenceFrame";
|
||||
}
|
||||
|
||||
static canvas::Element::ReferenceFrame defVal()
|
||||
{
|
||||
return canvas::Element::GLOBAL;
|
||||
}
|
||||
|
||||
static bool validate(int frame)
|
||||
{
|
||||
return frame >= canvas::Element::GLOBAL
|
||||
&& frame <= canvas::Element::LOCAL;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* CANVAS_ELEMENT_HXX_ */
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace canvas
|
||||
template<typename ElementType>
|
||||
void add(ElementFactories& factories)
|
||||
{
|
||||
ElementType::staticInit();
|
||||
factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
|
||||
}
|
||||
|
||||
@@ -45,6 +46,26 @@ namespace canvas
|
||||
ElementFactories Group::_child_factories;
|
||||
const std::string Group::TYPE_NAME = "group";
|
||||
|
||||
void warnTransformExpired(const char* member_name)
|
||||
{
|
||||
SG_LOG( SG_GENERAL,
|
||||
SG_WARN,
|
||||
"canvas::Group::" << member_name << ": Group has expired." );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Group::staticInit()
|
||||
{
|
||||
if( isInit<Group>() )
|
||||
return;
|
||||
|
||||
add<Group>(_child_factories);
|
||||
add<Image>(_child_factories);
|
||||
add<Map >(_child_factories);
|
||||
add<Path >(_child_factories);
|
||||
add<Text >(_child_factories);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Group::Group( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -52,14 +73,7 @@ namespace canvas
|
||||
Element* parent ):
|
||||
Element(canvas, node, parent_style, parent)
|
||||
{
|
||||
if( !isInit<Group>() )
|
||||
{
|
||||
add<Group>(_child_factories);
|
||||
add<Image>(_child_factories);
|
||||
add<Map >(_child_factories);
|
||||
add<Path >(_child_factories);
|
||||
add<Text >(_child_factories);
|
||||
}
|
||||
staticInit();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -119,8 +133,13 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
ElementPtr Group::getElementById(const std::string& id)
|
||||
{
|
||||
std::vector<GroupPtr> groups;
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
warnTransformExpired("getElementById");
|
||||
return ElementPtr();
|
||||
}
|
||||
|
||||
std::vector<GroupPtr> groups;
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
const ElementPtr& el = getChildByIndex(i);
|
||||
@@ -145,6 +164,8 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Group::clearEventListener()
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
return warnTransformExpired("clearEventListener");
|
||||
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
getChildByIndex(i)->clearEventListener();
|
||||
@@ -174,21 +195,23 @@ namespace canvas
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool Group::setStyle(const SGPropertyNode* style)
|
||||
bool Group::setStyle( const SGPropertyNode* style,
|
||||
const StyleInfo* style_info )
|
||||
{
|
||||
// Don't propagate styles directly applicable to this group
|
||||
if( Element::setStyle(style) )
|
||||
return true;
|
||||
|
||||
if( style->getParent() != _node
|
||||
&& _style.find(style->getNameString()) != _style.end() )
|
||||
if( !canApplyStyle(style) )
|
||||
return false;
|
||||
|
||||
bool handled = false;
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
bool handled = setStyleImpl(style, style_info);
|
||||
if( style_info->inheritable )
|
||||
{
|
||||
if( getChildByIndex(i)->setStyle(style) )
|
||||
handled = true;
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
warnTransformExpired("setStyle");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
handled |= getChildByIndex(i)->setStyle(style, style_info);
|
||||
}
|
||||
|
||||
return handled;
|
||||
@@ -198,6 +221,11 @@ namespace canvas
|
||||
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
|
||||
{
|
||||
osg::BoundingBox bb;
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
warnTransformExpired("getTransformedBounds");
|
||||
return bb;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
@@ -236,6 +264,9 @@ namespace canvas
|
||||
ElementFactory child_factory = getChildFactory( child->getNameString() );
|
||||
if( child_factory )
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
return warnTransformExpired("childAdded");
|
||||
|
||||
ElementPtr element = child_factory(_canvas, child, _style, this);
|
||||
|
||||
// Add to osg scene graph...
|
||||
@@ -247,12 +278,9 @@ namespace canvas
|
||||
return;
|
||||
}
|
||||
|
||||
if( !Element::setStyle(child) )
|
||||
{
|
||||
// Only add style if not applicable to group itself
|
||||
StyleInfo const* style = getStyleInfo(child->getNameString());
|
||||
if( style && style->inheritable )
|
||||
_style[ child->getNameString() ] = child;
|
||||
setStyle(child);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -285,9 +313,7 @@ namespace canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
Style::iterator style = _style.find(node->getNameString());
|
||||
if( style != _style.end() )
|
||||
_style.erase(style);
|
||||
_style.erase( node->getNameString() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +329,7 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Group::handleZIndexChanged(ElementPtr child, int z_index)
|
||||
{
|
||||
if( !child )
|
||||
if( !child || !_transform.valid() )
|
||||
return;
|
||||
|
||||
osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform();
|
||||
@@ -364,6 +390,12 @@ namespace canvas
|
||||
ElementPtr Group::findChild( const SGPropertyNode* node,
|
||||
const std::string& id ) const
|
||||
{
|
||||
if( !_transform.valid() )
|
||||
{
|
||||
warnTransformExpired("findChild");
|
||||
return ElementPtr();
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
|
||||
{
|
||||
ElementPtr el = getChildByIndex(i);
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace canvas
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
typedef std::list< std::pair< const SGPropertyNode*,
|
||||
ElementPtr
|
||||
@@ -92,7 +93,8 @@ namespace canvas
|
||||
|
||||
virtual bool traverse(EventVisitor& visitor);
|
||||
|
||||
virtual bool setStyle(const SGPropertyNode* child);
|
||||
virtual bool setStyle( const SGPropertyNode* child,
|
||||
const StyleInfo* style_info = 0 );
|
||||
|
||||
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
|
||||
|
||||
|
||||
@@ -89,6 +89,18 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string Image::TYPE_NAME = "image";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Image::staticInit()
|
||||
{
|
||||
if( isInit<Image>() )
|
||||
return;
|
||||
|
||||
addStyle("fill", "color", &Image::setFill);
|
||||
addStyle("slice", "", &Image::setSlice);
|
||||
addStyle("slice-width", "", &Image::setSliceWidth);
|
||||
addStyle("outset", "", &Image::setOutset);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Image::Image( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -100,6 +112,8 @@ namespace canvas
|
||||
_src_rect(0,0),
|
||||
_region(0,0)
|
||||
{
|
||||
staticInit();
|
||||
|
||||
_geom = new osg::Geometry;
|
||||
_geom->setUseDisplayList(false);
|
||||
|
||||
@@ -128,16 +142,7 @@ namespace canvas
|
||||
|
||||
setDrawable(_geom);
|
||||
|
||||
if( !isInit<Image>() )
|
||||
{
|
||||
addStyle("fill", "color", &Image::setFill);
|
||||
addStyle("slice", "", &Image::setSlice);
|
||||
addStyle("slice-width", "", &Image::setSliceWidth);
|
||||
addStyle("outset", "", &Image::setOutset);
|
||||
}
|
||||
|
||||
setFill("#ffffff"); // TODO how should we handle default values?
|
||||
|
||||
setupStyle();
|
||||
}
|
||||
|
||||
@@ -400,8 +405,9 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Image::setFill(const std::string& fill)
|
||||
{
|
||||
osg::Vec4 color;
|
||||
if( !parseColor(fill, color) )
|
||||
osg::Vec4 color(1,1,1,1);
|
||||
if( !fill.empty() // If no color is given default to white
|
||||
&& !parseColor(fill, color) )
|
||||
return;
|
||||
|
||||
_colors->front() = color;
|
||||
@@ -559,7 +565,7 @@ namespace canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
setImage( canvas->getSystemAdapter()->getImage(path) );
|
||||
setImage( Canvas::getSystemAdapter()->getImage(path) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace canvas
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
/**
|
||||
* @param node Property node containing settings for this image:
|
||||
|
||||
@@ -46,6 +46,17 @@ namespace canvas
|
||||
const std::string GEO = "-geo";
|
||||
const std::string Map::TYPE_NAME = "map";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Map::staticInit()
|
||||
{
|
||||
Group::staticInit();
|
||||
|
||||
if( isInit<Map>() )
|
||||
return;
|
||||
|
||||
// Do some initialization if needed...
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Map::Map( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -56,7 +67,7 @@ namespace canvas
|
||||
_projection(new SansonFlamsteedProjection),
|
||||
_projection_dirty(true)
|
||||
{
|
||||
|
||||
staticInit();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -194,6 +205,8 @@ namespace canvas
|
||||
_projection->setOrientation(child->getFloatValue());
|
||||
else if( child->getNameString() == "range" )
|
||||
_projection->setRange(child->getDoubleValue());
|
||||
else if( child->getNameString() == "screen-range" )
|
||||
_projection->setScreenRange(child->getDoubleValue());
|
||||
else
|
||||
return Group::childChanged(child);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace canvas
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
Map( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace canvas
|
||||
*/
|
||||
void setFill(const std::string& fill)
|
||||
{
|
||||
if( fill == "none" )
|
||||
if( fill.empty() || fill == "none" )
|
||||
{
|
||||
_mode &= ~VG_FILL_PATH;
|
||||
}
|
||||
@@ -150,7 +150,7 @@ namespace canvas
|
||||
*/
|
||||
void setStroke(const std::string& stroke)
|
||||
{
|
||||
if( stroke == "none" )
|
||||
if( stroke.empty() || stroke == "none" )
|
||||
{
|
||||
_mode &= ~VG_STROKE_PATH;
|
||||
}
|
||||
@@ -472,6 +472,22 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string Path::TYPE_NAME = "path";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Path::staticInit()
|
||||
{
|
||||
if( isInit<Path>() )
|
||||
return;
|
||||
|
||||
PathDrawableRef Path::*path = &Path::_path;
|
||||
|
||||
addStyle("fill", "color", &PathDrawable::setFill, path);
|
||||
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
|
||||
addStyle("stroke", "color", &PathDrawable::setStroke, path);
|
||||
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
|
||||
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
|
||||
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Path::Path( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -480,20 +496,9 @@ namespace canvas
|
||||
Element(canvas, node, parent_style, parent),
|
||||
_path( new PathDrawable(this) )
|
||||
{
|
||||
staticInit();
|
||||
|
||||
setDrawable(_path);
|
||||
|
||||
if( !isInit<Path>() )
|
||||
{
|
||||
PathDrawableRef Path::*path = &Path::_path;
|
||||
|
||||
addStyle("fill", "color", &PathDrawable::setFill, path);
|
||||
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
|
||||
addStyle("stroke", "color", &PathDrawable::setStroke, path);
|
||||
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
|
||||
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
|
||||
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
|
||||
}
|
||||
|
||||
setupStyle();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace canvas
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
Path( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
#include <simgear/canvas/CanvasSystemAdapter.hxx>
|
||||
#include <simgear/scene/util/parse_color.hxx>
|
||||
#include <simgear/structure/OSGVersion.hxx>
|
||||
#include <osg/Version>
|
||||
#include <osgText/Text>
|
||||
|
||||
namespace simgear
|
||||
@@ -36,6 +36,7 @@ namespace canvas
|
||||
|
||||
void setFontResolution(int res);
|
||||
void setCharacterAspect(float aspect);
|
||||
void setLineHeight(float factor);
|
||||
void setFill(const std::string& fill);
|
||||
void setBackgroundColor(const std::string& fill);
|
||||
|
||||
@@ -54,7 +55,7 @@ namespace canvas
|
||||
Text::TextOSG::TextOSG(canvas::Text* text):
|
||||
_text_element(text)
|
||||
{
|
||||
|
||||
setBackdropImplementation(NO_DEPTH_BUFFER);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -69,6 +70,12 @@ namespace canvas
|
||||
setCharacterSize(getCharacterHeight(), aspect);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::TextOSG::setLineHeight(float factor)
|
||||
{
|
||||
setLineSpacing(factor - 1);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::TextOSG::setFill(const std::string& fill)
|
||||
{
|
||||
@@ -175,7 +182,7 @@ namespace canvas
|
||||
if( !bb.valid() )
|
||||
return bb;
|
||||
|
||||
#if SG_OSG_VERSION_LESS_THAN(3,1,0)
|
||||
#if OSG_VERSION_LESS_THAN(3,1,0)
|
||||
// TODO bounding box still doesn't seem always right (eg. with center
|
||||
// horizontal alignment not completely accurate)
|
||||
bb._min.y() += _offset.y();
|
||||
@@ -254,6 +261,39 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string Text::TYPE_NAME = "text";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::staticInit()
|
||||
{
|
||||
if( isInit<Text>() )
|
||||
return;
|
||||
|
||||
osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
|
||||
|
||||
addStyle("fill", "color", &TextOSG::setFill, text);
|
||||
addStyle("background", "color", &TextOSG::setBackgroundColor, text);
|
||||
addStyle("character-size",
|
||||
"numeric",
|
||||
static_cast<
|
||||
void (TextOSG::*)(float)
|
||||
> (&TextOSG::setCharacterSize),
|
||||
text);
|
||||
addStyle("character-aspect-ratio",
|
||||
"numeric",
|
||||
&TextOSG::setCharacterAspect, text);
|
||||
addStyle("line-height", "numeric", &TextOSG::setLineHeight, text);
|
||||
addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
|
||||
addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
|
||||
// TEXT = 1 default
|
||||
// BOUNDINGBOX = 2
|
||||
// FILLEDBOUNDINGBOX = 4
|
||||
// ALIGNMENT = 8
|
||||
addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
|
||||
addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
|
||||
addStyle("font", "", &Text::setFont);
|
||||
addStyle("alignment", "", &Text::setAlignment);
|
||||
addStyle("text", "", &Text::setText, false);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
Text::Text( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
@@ -262,39 +302,13 @@ namespace canvas
|
||||
Element(canvas, node, parent_style, parent),
|
||||
_text( new Text::TextOSG(this) )
|
||||
{
|
||||
staticInit();
|
||||
|
||||
setDrawable(_text);
|
||||
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
|
||||
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
|
||||
_text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
|
||||
|
||||
if( !isInit<Text>() )
|
||||
{
|
||||
osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
|
||||
|
||||
addStyle("fill", "color", &TextOSG::setFill, text);
|
||||
addStyle("background", "color", &TextOSG::setBackgroundColor, text);
|
||||
addStyle("character-size",
|
||||
"numeric",
|
||||
static_cast<
|
||||
void (TextOSG::*)(float)
|
||||
> (&TextOSG::setCharacterSize),
|
||||
text);
|
||||
addStyle("character-aspect-ratio",
|
||||
"numeric",
|
||||
&TextOSG::setCharacterAspect, text);
|
||||
addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
|
||||
addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
|
||||
// TEXT = 1 default
|
||||
// BOUNDINGBOX = 2
|
||||
// FILLEDBOUNDINGBOX = 4
|
||||
// ALIGNMENT = 8
|
||||
addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
|
||||
addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
|
||||
addStyle("font", "", &Text::setFont);
|
||||
addStyle("alignment", "", &Text::setAlignment);
|
||||
addStyle("text", "", &Text::setText);
|
||||
}
|
||||
|
||||
setupStyle();
|
||||
}
|
||||
|
||||
@@ -313,7 +327,7 @@ namespace canvas
|
||||
//----------------------------------------------------------------------------
|
||||
void Text::setFont(const char* name)
|
||||
{
|
||||
_text->setFont( _canvas.lock()->getSystemAdapter()->getFont(name) );
|
||||
_text->setFont( Canvas::getSystemAdapter()->getFont(name) );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace canvas
|
||||
{
|
||||
public:
|
||||
static const std::string TYPE_NAME;
|
||||
static void staticInit();
|
||||
|
||||
Text( const CanvasWeakPtr& canvas,
|
||||
const SGPropertyNode_ptr& node,
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
# warning GCC compilers prior to 3.4 are suspect
|
||||
# endif
|
||||
|
||||
# define GCC_VERSION (__GNUC__ * 10000 \
|
||||
# define SG_GCC_VERSION (__GNUC__ * 10000 \
|
||||
+ __GNUC_MINOR__ * 100 \
|
||||
+ __GNUC_PATCHLEVEL__)
|
||||
# define SG_COMPILER_STR "GNU C++ version " SG_STRINGIZE(__GNUC__) "." SG_STRINGIZE(__GNUC_MINOR__)
|
||||
|
||||
@@ -34,18 +34,15 @@ class BufferedLogCallback::BufferedLogCallbackPrivate
|
||||
{
|
||||
public:
|
||||
SGMutex m_mutex;
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
vector_cstring m_buffer;
|
||||
unsigned int m_stamp;
|
||||
unsigned int m_maxLength;
|
||||
};
|
||||
|
||||
BufferedLogCallback::BufferedLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c,p),
|
||||
d(new BufferedLogCallbackPrivate)
|
||||
{
|
||||
d->m_class = c;
|
||||
d->m_priority = p;
|
||||
d->m_stamp = 0;
|
||||
d->m_maxLength = 0xffff;
|
||||
}
|
||||
@@ -63,7 +60,7 @@ void BufferedLogCallback::operator()(sgDebugClass c, sgDebugPriority p,
|
||||
SG_UNUSED(file);
|
||||
SG_UNUSED(line);
|
||||
|
||||
if ((c & d->m_class) == 0 || p < d->m_priority) return;
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
vector_cstring::value_type msg;
|
||||
if (aMessage.size() >= d->m_maxLength) {
|
||||
|
||||
@@ -20,10 +20,13 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "logstream.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
@@ -35,8 +38,8 @@
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
#ifdef _WIN32
|
||||
// for AllocConsole
|
||||
#ifdef SG_WINDOWS
|
||||
// for AllocConsole, OutputDebugString
|
||||
#include "windows.h"
|
||||
#endif
|
||||
|
||||
@@ -73,37 +76,57 @@ const char* debugClassToString(sgDebugClass c)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
m_class(c),
|
||||
m_priority(p)
|
||||
{
|
||||
}
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
return ((c & m_class) != 0 && p >= m_priority);
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class FileLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
FileLogCallback(const std::string& aPath, sgDebugClass c, sgDebugPriority p) :
|
||||
m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc),
|
||||
m_class(c),
|
||||
m_priority(p)
|
||||
simgear::LogCallback(c, p),
|
||||
m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message)
|
||||
{
|
||||
if ((c & m_class) == 0 || p < m_priority) return;
|
||||
if (!shouldLog(c, p)) return;
|
||||
m_file << debugClassToString(c) << ":" << (int) p
|
||||
<< ":" << file << ":" << line << ":" << message << std::endl;
|
||||
}
|
||||
private:
|
||||
std::ofstream m_file;
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
class StderrLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
m_class(c),
|
||||
m_priority(p)
|
||||
simgear::LogCallback(c, p)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
#ifdef SG_WINDOWS
|
||||
AllocConsole(); // but only if we want a console
|
||||
freopen("conin$", "r", stdin);
|
||||
freopen("conout$", "w", stdout);
|
||||
@@ -111,29 +134,42 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
void setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
if ((c & m_class) == 0 || p < m_priority) return;
|
||||
|
||||
// if running under MSVC, we could use OutputDebugString here
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
fprintf(stderr, "%s\n", aMessage.c_str());
|
||||
//fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
|
||||
// file, line, aMessage.c_str());
|
||||
fflush(stderr);
|
||||
}
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
|
||||
#ifdef SG_WINDOWS
|
||||
|
||||
class WinDebugLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c, p)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
std::ostringstream os;
|
||||
os << debugClassToString(c) << ":" << aMessage << std::endl;
|
||||
OutputDebugStringA(os.str().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class LogStreamPrivate : public SGThread
|
||||
{
|
||||
private:
|
||||
@@ -181,10 +217,20 @@ public:
|
||||
LogStreamPrivate() :
|
||||
m_logClass(SG_ALL),
|
||||
m_logPriority(SG_ALERT),
|
||||
m_isRunning(false)
|
||||
m_isRunning(false),
|
||||
m_consoleRequested(false)
|
||||
{
|
||||
m_stderrCallback = new StderrLogCallback(m_logClass, m_logPriority);
|
||||
addCallback(m_stderrCallback);
|
||||
|
||||
#if !defined(SG_WINDOWS)
|
||||
m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
|
||||
m_consoleCallbacks.push_back(m_callbacks.back());
|
||||
m_consoleRequested = true;
|
||||
#endif
|
||||
|
||||
#if defined (SG_WINDOWS) && !defined(NDEBUG)
|
||||
m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
|
||||
m_consoleCallbacks.push_back(m_callbacks.back());
|
||||
#endif
|
||||
}
|
||||
|
||||
SGMutex m_lock;
|
||||
@@ -192,11 +238,14 @@ public:
|
||||
|
||||
typedef std::vector<simgear::LogCallback*> CallbackVec;
|
||||
CallbackVec m_callbacks;
|
||||
|
||||
/// subset of callbacks which correspond to stdout / console,
|
||||
/// and hence should dynamically reflect console logging settings
|
||||
CallbackVec m_consoleCallbacks;
|
||||
|
||||
sgDebugClass m_logClass;
|
||||
sgDebugPriority m_logPriority;
|
||||
bool m_isRunning;
|
||||
StderrLogCallback* m_stderrCallback;
|
||||
bool m_consoleRequested;
|
||||
|
||||
void startLog()
|
||||
{
|
||||
@@ -260,7 +309,9 @@ public:
|
||||
PauseThread pause(this);
|
||||
m_logPriority = p;
|
||||
m_logClass = c;
|
||||
m_stderrCallback->setLogLevels(c, p);
|
||||
BOOST_FOREACH(simgear::LogCallback* cb, m_consoleCallbacks) {
|
||||
cb->setLogLevels(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
bool would_log( sgDebugClass c, sgDebugPriority p ) const
|
||||
@@ -275,6 +326,18 @@ public:
|
||||
LogEntry entry(c, p, fileName, line, msg);
|
||||
m_entries.push(entry);
|
||||
}
|
||||
|
||||
void requestConsole()
|
||||
{
|
||||
PauseThread pause(this);
|
||||
if (m_consoleRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_consoleRequested = true;
|
||||
m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
|
||||
m_consoleCallbacks.push_back(m_callbacks.back());
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -348,6 +411,13 @@ sglog()
|
||||
{
|
||||
// Force initialization of cerr.
|
||||
static std::ios_base::Init initializer;
|
||||
|
||||
// http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
|
||||
// in the absence of portable memory barrier ops in Simgear,
|
||||
// let's keep this correct & safe
|
||||
static SGMutex m;
|
||||
SGGuard<SGMutex> g(m);
|
||||
|
||||
if( !global_logstream )
|
||||
global_logstream = new logstream();
|
||||
return *global_logstream;
|
||||
@@ -359,3 +429,13 @@ logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
|
||||
global_privateLogstream->addCallback(new FileLogCallback(aPath.str(), c, p));
|
||||
}
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
void requestConsole()
|
||||
{
|
||||
sglog(); // force creation
|
||||
global_privateLogstream->requestConsole();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
@@ -42,7 +42,23 @@ public:
|
||||
virtual ~LogCallback() {}
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) = 0;
|
||||
|
||||
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||
protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper force a console on platforms where it might optional, when
|
||||
* we need to show a console. This basically means Windows at the
|
||||
* moment - on other plaforms it's a no-op
|
||||
*/
|
||||
void requestConsole();
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -103,6 +119,8 @@ private:
|
||||
|
||||
logstream& sglog();
|
||||
|
||||
|
||||
|
||||
/** \def SG_LOG(C,P,M)
|
||||
* Log a message.
|
||||
* @param C debug class
|
||||
|
||||
@@ -629,7 +629,7 @@ bool SGMetar::scanWeather()
|
||||
weather = pre + weather + post;
|
||||
weather.erase(weather.length() - 1);
|
||||
_weather.push_back(weather);
|
||||
if( w.phenomena.size() > 0 )
|
||||
if( ! w.phenomena.empty() )
|
||||
_weather2.push_back( w );
|
||||
_grpcount++;
|
||||
return true;
|
||||
|
||||
@@ -42,7 +42,6 @@ private:
|
||||
float _rain_intensity;
|
||||
float _clip_distance;
|
||||
|
||||
int _wind_dir;
|
||||
osg::Vec3 _wind_vec;
|
||||
|
||||
osg::ref_ptr<osgParticle::PrecipitationEffect> _precipitationEffect;
|
||||
|
||||
@@ -15,7 +15,10 @@ set(HEADERS
|
||||
sg_socket.hxx
|
||||
sg_socket_udp.hxx
|
||||
HTTPClient.hxx
|
||||
HTTPFileRequest.hxx
|
||||
HTTPMemoryRequest.hxx
|
||||
HTTPRequest.hxx
|
||||
HTTPContentDecode.hxx
|
||||
DAVMultiStatus.hxx
|
||||
SVNRepository.hxx
|
||||
SVNDirectory.hxx
|
||||
@@ -35,7 +38,10 @@ set(SOURCES
|
||||
sg_socket.cxx
|
||||
sg_socket_udp.cxx
|
||||
HTTPClient.cxx
|
||||
HTTPFileRequest.cxx
|
||||
HTTPMemoryRequest.cxx
|
||||
HTTPRequest.cxx
|
||||
HTTPContentDecode.cxx
|
||||
DAVMultiStatus.cxx
|
||||
SVNRepository.cxx
|
||||
SVNDirectory.cxx
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include "DAVMultiStatus.hxx"
|
||||
|
||||
#include <iostream>
|
||||
@@ -27,10 +31,15 @@
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "simgear/debug/logstream.hxx"
|
||||
#include "simgear/xml/xmlparse.h"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include "simgear/structure/exception.hxx"
|
||||
|
||||
#ifdef SYSTEM_EXPAT
|
||||
# include <expat.h>
|
||||
#else
|
||||
# include "sg_expat.h"
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
|
||||
using namespace simgear;
|
||||
@@ -58,7 +67,10 @@ DAVResource::DAVResource(const string& href) :
|
||||
_url(href),
|
||||
_container(NULL)
|
||||
{
|
||||
assert(!href.empty());
|
||||
assert(!href.empty());
|
||||
if (strutils::ends_with(href, "/")) {
|
||||
_url = href.substr(0, _url.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DAVResource::setVersionName(const std::string& aVersion)
|
||||
@@ -171,7 +183,8 @@ class DAVMultiStatus::DAVMultiStatusPrivate
|
||||
{
|
||||
public:
|
||||
DAVMultiStatusPrivate() :
|
||||
parserInited(false)
|
||||
parserInited(false),
|
||||
valid(false)
|
||||
{
|
||||
rootResource = NULL;
|
||||
}
|
||||
@@ -275,6 +288,7 @@ public:
|
||||
}
|
||||
|
||||
bool parserInited;
|
||||
bool valid;
|
||||
XML_Parser xmlParser;
|
||||
DAVResource* rootResource;
|
||||
|
||||
@@ -354,13 +368,21 @@ void DAVMultiStatus::parseXML(const char* data, int size)
|
||||
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
_d->parserInited = false;
|
||||
_d->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DAVMultiStatus::finishParse()
|
||||
{
|
||||
if (_d->parserInited) {
|
||||
XML_Parse(_d->xmlParser, NULL, 0, true);
|
||||
if (!XML_Parse(_d->xmlParser, NULL, 0, true)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "DAV parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
|
||||
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
|
||||
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
|
||||
_d->valid = false;
|
||||
} else {
|
||||
_d->valid = true;
|
||||
}
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
}
|
||||
|
||||
@@ -372,4 +394,9 @@ DAVResource* DAVMultiStatus::resource()
|
||||
return _d->rootResource;
|
||||
}
|
||||
|
||||
bool DAVMultiStatus::isValid() const
|
||||
{
|
||||
return _d->valid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ public:
|
||||
|
||||
void finishParse();
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
DAVResource* resource();
|
||||
|
||||
class DAVMultiStatusPrivate;
|
||||
|
||||
@@ -1,22 +1,47 @@
|
||||
/**
|
||||
* \file HTTPClient.cxx - simple HTTP client engine for SimHear
|
||||
*/
|
||||
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPFileRequest.hxx"
|
||||
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <cstdlib> // rand()
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
#include <simgear/io/lowlevel.hxx>
|
||||
#include <simgear/io/HTTPContentDecode.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
|
||||
#include "version.h"
|
||||
@@ -26,10 +51,6 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::vector;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
@@ -39,21 +60,31 @@ namespace HTTP
|
||||
extern const int DEFAULT_HTTP_PORT = 80;
|
||||
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||
const unsigned int MAX_INFLIGHT_REQUESTS = 32;
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
|
||||
|
||||
// see http://www.ietf.org/rfc/rfc1952.txt for these values and
|
||||
// detailed description of the logic
|
||||
const int GZIP_HEADER_ID1 = 31;
|
||||
const int GZIP_HEADER_ID2 = 139;
|
||||
const int GZIP_HEADER_METHOD_DEFLATE = 8;
|
||||
const unsigned int GZIP_HEADER_SIZE = 10;
|
||||
const int GZIP_HEADER_FEXTRA = 1 << 2;
|
||||
const int GZIP_HEADER_FNAME = 1 << 3;
|
||||
const int GZIP_HEADER_COMMENT = 1 << 4;
|
||||
const int GZIP_HEADER_CRC = 1 << 1;
|
||||
|
||||
|
||||
class Connection;
|
||||
typedef std::multimap<std::string, Connection*> ConnectionDict;
|
||||
typedef std::list<Request_ptr> RequestList;
|
||||
|
||||
class Client::ClientPrivate
|
||||
{
|
||||
public:
|
||||
std::string userAgent;
|
||||
std::string proxy;
|
||||
int proxyPort;
|
||||
std::string proxyAuth;
|
||||
NetChannelPoller poller;
|
||||
unsigned int maxConnections;
|
||||
|
||||
RequestList pendingRequests;
|
||||
|
||||
// connections by host (potentially more than one)
|
||||
ConnectionDict connections;
|
||||
|
||||
SGTimeStamp timeTransferSample;
|
||||
unsigned int bytesTransferred;
|
||||
unsigned int lastTransferRate;
|
||||
uint64_t totalBytesDownloaded;
|
||||
};
|
||||
|
||||
class Connection : public NetChat
|
||||
{
|
||||
@@ -61,26 +92,28 @@ public:
|
||||
Connection(Client* pr) :
|
||||
client(pr),
|
||||
state(STATE_CLOSED),
|
||||
port(DEFAULT_HTTP_PORT),
|
||||
zlibInflateBuffer(NULL),
|
||||
zlibInflateBufferSize(0),
|
||||
zlibOutputBuffer(NULL)
|
||||
port(DEFAULT_HTTP_PORT)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual ~Connection()
|
||||
{
|
||||
if (zlibInflateBuffer) {
|
||||
free(zlibInflateBuffer);
|
||||
}
|
||||
|
||||
if (zlibOutputBuffer) {
|
||||
free(zlibOutputBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void handleBufferRead (NetBuffer& buffer)
|
||||
{
|
||||
if( !activeRequest || !activeRequest->isComplete() )
|
||||
return NetChat::handleBufferRead(buffer);
|
||||
|
||||
// Request should be aborted (signaled by setting its state to complete).
|
||||
|
||||
// force the state to GETTING_BODY, to simplify logic in
|
||||
// responseComplete and handleClose
|
||||
state = STATE_GETTING_BODY;
|
||||
responseComplete();
|
||||
}
|
||||
|
||||
void setServer(const string& h, short p)
|
||||
void setServer(const std::string& h, short p)
|
||||
{
|
||||
host = h;
|
||||
port = p;
|
||||
@@ -111,6 +144,7 @@ public:
|
||||
SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
|
||||
activeRequest->setFailure(error, "socket error");
|
||||
activeRequest = NULL;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
state = STATE_SOCKET_ERROR;
|
||||
@@ -138,6 +172,7 @@ public:
|
||||
sentRequests.erase(it);
|
||||
}
|
||||
activeRequest = NULL;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
state = STATE_CLOSED;
|
||||
@@ -154,18 +189,37 @@ public:
|
||||
sentRequests.clear();
|
||||
}
|
||||
|
||||
void handleTimeout()
|
||||
{
|
||||
NetChat::handleError(ETIMEDOUT);
|
||||
if (activeRequest) {
|
||||
SG_LOG(SG_IO, SG_DEBUG, "HTTP socket timeout");
|
||||
activeRequest->setFailure(ETIMEDOUT, "socket timeout");
|
||||
activeRequest = NULL;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
state = STATE_SOCKET_ERROR;
|
||||
}
|
||||
|
||||
void queueRequest(const Request_ptr& r)
|
||||
{
|
||||
queuedRequests.push_back(r);
|
||||
tryStartNextRequest();
|
||||
queuedRequests.push_back(r);
|
||||
tryStartNextRequest();
|
||||
}
|
||||
|
||||
void beginResponse()
|
||||
{
|
||||
assert(!sentRequests.empty());
|
||||
|
||||
activeRequest = sentRequests.front();
|
||||
activeRequest->responseStart(buffer);
|
||||
assert(!sentRequests.empty());
|
||||
assert(state == STATE_WAITING_FOR_RESPONSE);
|
||||
|
||||
activeRequest = sentRequests.front();
|
||||
try {
|
||||
activeRequest->responseStart(buffer);
|
||||
} catch (sg_exception& e) {
|
||||
handleError(EIO);
|
||||
}
|
||||
|
||||
state = STATE_GETTING_HEADERS;
|
||||
buffer.clear();
|
||||
if (activeRequest->responseCode() == 204) {
|
||||
@@ -178,11 +232,15 @@ public:
|
||||
|
||||
bodyTransferSize = -1;
|
||||
chunkedTransfer = false;
|
||||
contentGZip = contentDeflate = false;
|
||||
_contentDecoder.reset();
|
||||
}
|
||||
|
||||
void tryStartNextRequest()
|
||||
{
|
||||
while( !queuedRequests.empty()
|
||||
&& queuedRequests.front()->isComplete() )
|
||||
queuedRequests.pop_front();
|
||||
|
||||
if (queuedRequests.empty()) {
|
||||
idleTime.stamp();
|
||||
return;
|
||||
@@ -203,28 +261,28 @@ public:
|
||||
|
||||
Request_ptr r = queuedRequests.front();
|
||||
r->requestStart();
|
||||
requestBodyBytesToSend = r->requestBodyLength();
|
||||
|
||||
stringstream headerData;
|
||||
string path = r->path();
|
||||
|
||||
std::stringstream headerData;
|
||||
std::string path = r->path();
|
||||
assert(!path.empty());
|
||||
string query = r->query();
|
||||
string bodyData;
|
||||
std::string query = r->query();
|
||||
std::string bodyData;
|
||||
|
||||
if (!client->proxyHost().empty()) {
|
||||
path = r->scheme() + "://" + r->host() + r->path();
|
||||
}
|
||||
|
||||
if (r->requestBodyType() == CONTENT_TYPE_URL_ENCODED) {
|
||||
if (r->bodyType() == CONTENT_TYPE_URL_ENCODED) {
|
||||
headerData << r->method() << " " << path << " HTTP/1.1\r\n";
|
||||
bodyData = query.substr(1); // URL-encode, drop the leading '?'
|
||||
headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
|
||||
headerData << "Content-Length:" << bodyData.size() << "\r\n";
|
||||
} else {
|
||||
headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
|
||||
if (requestBodyBytesToSend >= 0) {
|
||||
headerData << "Content-Length:" << requestBodyBytesToSend << "\r\n";
|
||||
headerData << "Content-Type:" << r->requestBodyType() << "\r\n";
|
||||
if( r->hasBodyData() )
|
||||
{
|
||||
headerData << "Content-Length:" << r->bodyLength() << "\r\n";
|
||||
headerData << "Content-Type:" << r->bodyType() << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,8 +293,8 @@ public:
|
||||
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
|
||||
}
|
||||
|
||||
BOOST_FOREACH(string h, r->requestHeaders()) {
|
||||
headerData << h << ": " << r->header(h) << "\r\n";
|
||||
BOOST_FOREACH(const StringMap::value_type& h, r->requestHeaders()) {
|
||||
headerData << h.first << ": " << h.second << "\r\n";
|
||||
}
|
||||
|
||||
headerData << "\r\n"; // final CRLF to terminate the headers
|
||||
@@ -251,170 +309,62 @@ public:
|
||||
// drain down before trying to start any more requests.
|
||||
return;
|
||||
}
|
||||
|
||||
while (requestBodyBytesToSend > 0) {
|
||||
char buf[4096];
|
||||
int len = r->getBodyData(buf, 4096);
|
||||
if (len > 0) {
|
||||
requestBodyBytesToSend -= len;
|
||||
if (!bufferSend(buf, len)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "overflow the HTTP::Connection output buffer");
|
||||
state = STATE_SOCKET_ERROR;
|
||||
return;
|
||||
|
||||
if( r->hasBodyData() )
|
||||
for(size_t body_bytes_sent = 0; body_bytes_sent < r->bodyLength();)
|
||||
{
|
||||
char buf[4096];
|
||||
size_t len = r->getBodyData(buf, body_bytes_sent, 4096);
|
||||
if( len )
|
||||
{
|
||||
if( !bufferSend(buf, len) )
|
||||
{
|
||||
SG_LOG(SG_IO,
|
||||
SG_WARN,
|
||||
"overflow the HTTP::Connection output buffer");
|
||||
state = STATE_SOCKET_ERROR;
|
||||
return;
|
||||
}
|
||||
body_bytes_sent += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
SG_LOG(SG_IO,
|
||||
SG_WARN,
|
||||
"HTTP asynchronous request body generation is unsupported");
|
||||
break;
|
||||
}
|
||||
// SG_LOG(SG_IO, SG_INFO, "sent body:\n" << string(buf, len) << "\n%%%%%%%%%");
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP asynchronous request body generation is unsupported");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SG_LOG(SG_IO, SG_DEBUG, "did start request:" << r->url() <<
|
||||
"\n\t @ " << reinterpret_cast<void*>(r.ptr()) <<
|
||||
"\n\t on connection " << this);
|
||||
// successfully sent, remove from queue, and maybe send the next
|
||||
// SG_LOG(SG_IO, SG_INFO, "did start request:" << r->url() <<
|
||||
// "\n\t @ " << reinterpret_cast<void*>(r.ptr()) <<
|
||||
// "\n\t on connection " << this);
|
||||
// successfully sent, remove from queue, and maybe send the next
|
||||
queuedRequests.pop_front();
|
||||
sentRequests.push_back(r);
|
||||
|
||||
// pipelining, let's maybe send the next request right away
|
||||
state = STATE_WAITING_FOR_RESPONSE;
|
||||
|
||||
// pipelining, let's maybe send the next request right away
|
||||
tryStartNextRequest();
|
||||
}
|
||||
|
||||
virtual void collectIncomingData(const char* s, int n)
|
||||
{
|
||||
idleTime.stamp();
|
||||
if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) {
|
||||
if (contentGZip || contentDeflate) {
|
||||
expandCompressedData(s, n);
|
||||
} else {
|
||||
activeRequest->processBodyBytes(s, n);
|
||||
}
|
||||
} else {
|
||||
buffer += string(s, n);
|
||||
}
|
||||
client->receivedBytes(static_cast<unsigned int>(n));
|
||||
|
||||
if( (state == STATE_GETTING_BODY)
|
||||
|| (state == STATE_GETTING_CHUNKED_BYTES) )
|
||||
_contentDecoder.receivedBytes(s, n);
|
||||
else
|
||||
buffer.append(s, n);
|
||||
}
|
||||
|
||||
|
||||
void expandCompressedData(const char* s, int n)
|
||||
{
|
||||
int reqSize = n + zlib.avail_in;
|
||||
if (reqSize > zlibInflateBufferSize) {
|
||||
// reallocate
|
||||
unsigned char* newBuf = (unsigned char*) malloc(reqSize);
|
||||
memcpy(newBuf, zlib.next_in, zlib.avail_in);
|
||||
memcpy(newBuf + zlib.avail_in, s, n);
|
||||
free(zlibInflateBuffer);
|
||||
zlibInflateBuffer = newBuf;
|
||||
zlibInflateBufferSize = reqSize;
|
||||
} else {
|
||||
// important to use memmove here, since it's very likely
|
||||
// the source and destination ranges overlap
|
||||
memmove(zlibInflateBuffer, zlib.next_in, zlib.avail_in);
|
||||
memcpy(zlibInflateBuffer + zlib.avail_in, s, n);
|
||||
}
|
||||
|
||||
zlib.next_in = (unsigned char*) zlibInflateBuffer;
|
||||
zlib.avail_in = reqSize;
|
||||
zlib.next_out = zlibOutputBuffer;
|
||||
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
|
||||
if (contentGZip && !handleGZipHeader()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int writtenSize = 0;
|
||||
do {
|
||||
int result = inflate(&zlib, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP: got Zlib error:" << result);
|
||||
return;
|
||||
}
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlib.avail_out;
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
} while ((writtenSize == 0) && (zlib.avail_in > 0));
|
||||
|
||||
if (writtenSize > 0) {
|
||||
activeRequest->processBodyBytes((const char*) zlibOutputBuffer, writtenSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool handleGZipHeader()
|
||||
{
|
||||
// we clear this down to contentDeflate once the GZip header has been seen
|
||||
if (zlib.avail_in < GZIP_HEADER_SIZE) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
if ((zlibInflateBuffer[0] != GZIP_HEADER_ID1) ||
|
||||
(zlibInflateBuffer[1] != GZIP_HEADER_ID2) ||
|
||||
(zlibInflateBuffer[2] != GZIP_HEADER_METHOD_DEFLATE))
|
||||
{
|
||||
return false; // invalid GZip header
|
||||
}
|
||||
|
||||
char flags = zlibInflateBuffer[3];
|
||||
unsigned int gzipHeaderSize = GZIP_HEADER_SIZE;
|
||||
if (flags & GZIP_HEADER_FEXTRA) {
|
||||
gzipHeaderSize += 2;
|
||||
if (zlib.avail_in < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(zlibInflateBuffer + GZIP_HEADER_FEXTRA));
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( &extraHeaderBytes );
|
||||
}
|
||||
|
||||
gzipHeaderSize += extraHeaderBytes;
|
||||
if (zlib.avail_in < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_FNAME) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= zlib.avail_in) {
|
||||
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_COMMENT) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= zlib.avail_in) {
|
||||
if (zlibInflateBuffer[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_CRC) {
|
||||
gzipHeaderSize += 2;
|
||||
}
|
||||
|
||||
if (zlib.avail_in < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
zlib.next_in += gzipHeaderSize;
|
||||
zlib.avail_in -= gzipHeaderSize;
|
||||
// now we've processed the GZip header, can decode as deflate
|
||||
contentGZip = false;
|
||||
contentDeflate = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void foundTerminator(void)
|
||||
{
|
||||
idleTime.stamp();
|
||||
switch (state) {
|
||||
case STATE_IDLE:
|
||||
case STATE_WAITING_FOR_RESPONSE:
|
||||
beginResponse();
|
||||
break;
|
||||
|
||||
@@ -443,6 +393,9 @@ public:
|
||||
buffer.clear();
|
||||
break;
|
||||
|
||||
case STATE_IDLE:
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP got data in IDLE state, bad server?");
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -454,6 +407,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(sentRequests.empty());
|
||||
return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
|
||||
}
|
||||
|
||||
@@ -500,37 +454,9 @@ private:
|
||||
|
||||
void processHeader()
|
||||
{
|
||||
string h = strutils::simplify(buffer);
|
||||
std::string h = strutils::simplify(buffer);
|
||||
if (h.empty()) { // blank line terminates headers
|
||||
headersComplete();
|
||||
|
||||
if (contentGZip || contentDeflate) {
|
||||
memset(&zlib, 0, sizeof(z_stream));
|
||||
if (!zlibOutputBuffer) {
|
||||
zlibOutputBuffer = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
// NULLs means we'll get default alloc+free methods
|
||||
// which is absolutely fine
|
||||
zlib.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
zlib.next_out = zlibOutputBuffer;
|
||||
if (inflateInit2(&zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkedTransfer) {
|
||||
state = STATE_GETTING_CHUNKED;
|
||||
} else if (noMessageBody || (bodyTransferSize == 0)) {
|
||||
// force the state to GETTING_BODY, to simplify logic in
|
||||
// responseComplete and handleClose
|
||||
state = STATE_GETTING_BODY;
|
||||
responseComplete();
|
||||
} else {
|
||||
setByteCount(bodyTransferSize); // may be -1, that's fine
|
||||
state = STATE_GETTING_BODY;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -540,9 +466,9 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||
string lkey = boost::to_lower_copy(key);
|
||||
string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||
std::string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||
std::string lkey = boost::to_lower_copy(key);
|
||||
std::string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||
|
||||
// only consider these if getting headers (as opposed to trailers
|
||||
// of a chunked transfer)
|
||||
@@ -559,20 +485,14 @@ private:
|
||||
} else if (lkey == "transfer-encoding") {
|
||||
processTransferEncoding(value);
|
||||
} else if (lkey == "content-encoding") {
|
||||
if (value == "gzip") {
|
||||
contentGZip = true;
|
||||
} else if (value == "deflate") {
|
||||
contentDeflate = true;
|
||||
} else if (value != "identity") {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << value);
|
||||
}
|
||||
_contentDecoder.setEncoding(value);
|
||||
}
|
||||
}
|
||||
|
||||
activeRequest->responseHeader(lkey, value);
|
||||
}
|
||||
|
||||
void processTransferEncoding(const string& te)
|
||||
void processTransferEncoding(const std::string& te)
|
||||
{
|
||||
if (te == "chunked") {
|
||||
chunkedTransfer = true;
|
||||
@@ -623,17 +543,25 @@ private:
|
||||
void headersComplete()
|
||||
{
|
||||
activeRequest->responseHeadersComplete();
|
||||
_contentDecoder.initWithRequest(activeRequest);
|
||||
|
||||
if (chunkedTransfer) {
|
||||
state = STATE_GETTING_CHUNKED;
|
||||
} else if (noMessageBody || (bodyTransferSize == 0)) {
|
||||
// force the state to GETTING_BODY, to simplify logic in
|
||||
// responseComplete and handleClose
|
||||
state = STATE_GETTING_BODY;
|
||||
responseComplete();
|
||||
} else {
|
||||
setByteCount(bodyTransferSize); // may be -1, that's fine
|
||||
state = STATE_GETTING_BODY;
|
||||
}
|
||||
}
|
||||
|
||||
void responseComplete()
|
||||
{
|
||||
// SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url());
|
||||
activeRequest->responseComplete();
|
||||
client->requestFinished(this);
|
||||
|
||||
if (contentDeflate) {
|
||||
inflateEnd(&zlib);
|
||||
}
|
||||
Request_ptr completedRequest = activeRequest;
|
||||
_contentDecoder.finish();
|
||||
|
||||
assert(sentRequests.front() == activeRequest);
|
||||
sentRequests.pop_front();
|
||||
@@ -644,21 +572,29 @@ private:
|
||||
if (doClose) {
|
||||
// this will bring us into handleClose() above, which updates
|
||||
// state to STATE_CLOSED
|
||||
close();
|
||||
close();
|
||||
|
||||
// if we have additional requests waiting, try to start them now
|
||||
tryStartNextRequest();
|
||||
}
|
||||
tryStartNextRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if (state != STATE_CLOSED) {
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
setTerminator("\r\n");
|
||||
if (state != STATE_CLOSED) {
|
||||
state = sentRequests.empty() ? STATE_IDLE : STATE_WAITING_FOR_RESPONSE;
|
||||
}
|
||||
|
||||
// notify request after we change state, so this connection is idle
|
||||
// if completion triggers other requests (which is likely)
|
||||
// SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url());
|
||||
completedRequest->responseComplete();
|
||||
client->requestFinished(this);
|
||||
|
||||
setTerminator("\r\n");
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
STATE_IDLE = 0,
|
||||
STATE_WAITING_FOR_RESPONSE,
|
||||
STATE_GETTING_HEADERS,
|
||||
STATE_GETTING_BODY,
|
||||
STATE_GETTING_CHUNKED,
|
||||
@@ -671,75 +607,187 @@ private:
|
||||
Client* client;
|
||||
Request_ptr activeRequest;
|
||||
ConnectionState state;
|
||||
string host;
|
||||
std::string host;
|
||||
short port;
|
||||
std::string buffer;
|
||||
int bodyTransferSize;
|
||||
SGTimeStamp idleTime;
|
||||
bool chunkedTransfer;
|
||||
bool noMessageBody;
|
||||
int requestBodyBytesToSend;
|
||||
|
||||
z_stream zlib;
|
||||
unsigned char* zlibInflateBuffer;
|
||||
int zlibInflateBufferSize;
|
||||
unsigned char* zlibOutputBuffer;
|
||||
bool contentGZip, contentDeflate;
|
||||
|
||||
|
||||
RequestList queuedRequests;
|
||||
RequestList sentRequests;
|
||||
|
||||
ContentDecoder _contentDecoder;
|
||||
};
|
||||
|
||||
Client::Client()
|
||||
Client::Client() :
|
||||
d(new ClientPrivate)
|
||||
{
|
||||
d->proxyPort = 0;
|
||||
d->maxConnections = 4;
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
d->timeTransferSample.stamp();
|
||||
d->totalBytesDownloaded = 0;
|
||||
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
{
|
||||
}
|
||||
|
||||
void Client::setMaxConnections(unsigned int maxCon)
|
||||
{
|
||||
if (maxCon < 1) {
|
||||
throw sg_range_exception("illegal HTTP::Client::setMaxConnections value");
|
||||
}
|
||||
|
||||
d->maxConnections = maxCon;
|
||||
}
|
||||
|
||||
void Client::update(int waitTimeout)
|
||||
{
|
||||
_poller.poll(waitTimeout);
|
||||
|
||||
ConnectionDict::iterator it = _connections.begin();
|
||||
for (; it != _connections.end(); ) {
|
||||
if (it->second->hasIdleTimeout() || it->second->hasError() ||
|
||||
it->second->hasErrorTimeout())
|
||||
if (!d->poller.hasChannels() && (waitTimeout > 0)) {
|
||||
SGTimeStamp::sleepForMSec(waitTimeout);
|
||||
} else {
|
||||
d->poller.poll(waitTimeout);
|
||||
}
|
||||
|
||||
bool waitingRequests = !d->pendingRequests.empty();
|
||||
ConnectionDict::iterator it = d->connections.begin();
|
||||
for (; it != d->connections.end(); ) {
|
||||
Connection* con = it->second;
|
||||
if (con->hasIdleTimeout() ||
|
||||
con->hasError() ||
|
||||
con->hasErrorTimeout() ||
|
||||
(!con->isActive() && waitingRequests))
|
||||
{
|
||||
if (con->hasErrorTimeout()) {
|
||||
// tell the connection we're timing it out
|
||||
con->handleTimeout();
|
||||
}
|
||||
|
||||
// connection has been idle for a while, clean it up
|
||||
// (or has an error condition, again clean it up)
|
||||
// (or if we have requests waiting for a different host,
|
||||
// or an error condition
|
||||
ConnectionDict::iterator del = it++;
|
||||
delete del->second;
|
||||
_connections.erase(del);
|
||||
d->connections.erase(del);
|
||||
} else {
|
||||
if (it->second->shouldStartNext()) {
|
||||
it->second->tryStartNextRequest();
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
} // of connecion iteration
|
||||
} // of connection iteration
|
||||
|
||||
if (waitingRequests && (d->connections.size() < d->maxConnections)) {
|
||||
RequestList waiting(d->pendingRequests);
|
||||
d->pendingRequests.clear();
|
||||
|
||||
// re-submit all waiting requests in order; this takes care of
|
||||
// finding multiple pending items targetted to the same (new)
|
||||
// connection
|
||||
BOOST_FOREACH(Request_ptr req, waiting) {
|
||||
makeRequest(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::makeRequest(const Request_ptr& r)
|
||||
{
|
||||
string host = r->host();
|
||||
if( r->isComplete() )
|
||||
return;
|
||||
|
||||
if( r->url().find("://") == std::string::npos ) {
|
||||
r->setFailure(EINVAL, "malformed URL");
|
||||
return;
|
||||
}
|
||||
|
||||
if( r->url().find("http://") != 0 ) {
|
||||
r->setFailure(EINVAL, "only HTTP protocol is supported");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string host = r->host();
|
||||
int port = r->port();
|
||||
if (!_proxy.empty()) {
|
||||
host = _proxy;
|
||||
port = _proxyPort;
|
||||
if (!d->proxy.empty()) {
|
||||
host = d->proxy;
|
||||
port = d->proxyPort;
|
||||
}
|
||||
|
||||
stringstream ss;
|
||||
Connection* con = NULL;
|
||||
std::stringstream ss;
|
||||
ss << host << "-" << port;
|
||||
string connectionId = ss.str();
|
||||
|
||||
if (_connections.find(connectionId) == _connections.end()) {
|
||||
Connection* con = new Connection(this);
|
||||
con->setServer(host, port);
|
||||
_poller.addChannel(con);
|
||||
_connections[connectionId] = con;
|
||||
std::string connectionId = ss.str();
|
||||
bool havePending = !d->pendingRequests.empty();
|
||||
bool atConnectionsLimit = d->connections.size() >= d->maxConnections;
|
||||
ConnectionDict::iterator consEnd = d->connections.end();
|
||||
|
||||
// assign request to an existing Connection.
|
||||
// various options exist here, examined in order
|
||||
ConnectionDict::iterator it = d->connections.find(connectionId);
|
||||
if (atConnectionsLimit && (it == consEnd)) {
|
||||
// maximum number of connections active, queue this request
|
||||
// when a connection goes inactive, we'll start this one
|
||||
d->pendingRequests.push_back(r);
|
||||
return;
|
||||
}
|
||||
|
||||
_connections[connectionId]->queueRequest(r);
|
||||
// scan for an idle Connection to the same host (likely if we're
|
||||
// retrieving multiple resources from the same host in quick succession)
|
||||
// if we have pending requests (waiting for a free Connection), then
|
||||
// force new requests on this id to always use the first Connection
|
||||
// (instead of the random selection below). This ensures that when
|
||||
// there's pressure on the number of connections to keep alive, one
|
||||
// host can't DoS every other.
|
||||
int count = 0;
|
||||
for (; (it != consEnd) && (it->first == connectionId); ++it, ++count) {
|
||||
if (havePending || !it->second->isActive()) {
|
||||
con = it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!con && atConnectionsLimit) {
|
||||
// all current connections are busy (active), and we don't
|
||||
// have free connections to allocate, so let's assign to
|
||||
// an existing one randomly. Ideally we'd used whichever one will
|
||||
// complete first but we don't have that info.
|
||||
int index = rand() % count;
|
||||
for (it = d->connections.find(connectionId); index > 0; --index) { ; }
|
||||
con = it->second;
|
||||
}
|
||||
|
||||
// allocate a new connection object
|
||||
if (!con) {
|
||||
con = new Connection(this);
|
||||
con->setServer(host, port);
|
||||
d->poller.addChannel(con);
|
||||
d->connections.insert(d->connections.end(),
|
||||
ConnectionDict::value_type(connectionId, con));
|
||||
}
|
||||
|
||||
con->queueRequest(r);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
FileRequestRef Client::save( const std::string& url,
|
||||
const std::string& filename )
|
||||
{
|
||||
FileRequestRef req = new FileRequest(url, filename);
|
||||
makeRequest(req);
|
||||
return req;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
MemoryRequestRef Client::load(const std::string& url)
|
||||
{
|
||||
MemoryRequestRef req = new MemoryRequest(url);
|
||||
makeRequest(req);
|
||||
return req;
|
||||
}
|
||||
|
||||
void Client::requestFinished(Connection* con)
|
||||
@@ -747,28 +795,82 @@ void Client::requestFinished(Connection* con)
|
||||
|
||||
}
|
||||
|
||||
void Client::setUserAgent(const string& ua)
|
||||
void Client::setUserAgent(const std::string& ua)
|
||||
{
|
||||
_userAgent = ua;
|
||||
d->userAgent = ua;
|
||||
}
|
||||
|
||||
void Client::setProxy(const string& proxy, int port, const string& auth)
|
||||
const std::string& Client::userAgent() const
|
||||
{
|
||||
_proxy = proxy;
|
||||
_proxyPort = port;
|
||||
_proxyAuth = auth;
|
||||
return d->userAgent;
|
||||
}
|
||||
|
||||
const std::string& Client::proxyHost() const
|
||||
{
|
||||
return d->proxy;
|
||||
}
|
||||
|
||||
const std::string& Client::proxyAuth() const
|
||||
{
|
||||
return d->proxyAuth;
|
||||
}
|
||||
|
||||
void Client::setProxy( const std::string& proxy,
|
||||
int port,
|
||||
const std::string& auth )
|
||||
{
|
||||
d->proxy = proxy;
|
||||
d->proxyPort = port;
|
||||
d->proxyAuth = auth;
|
||||
}
|
||||
|
||||
bool Client::hasActiveRequests() const
|
||||
{
|
||||
ConnectionDict::const_iterator it = _connections.begin();
|
||||
for (; it != _connections.end(); ++it) {
|
||||
ConnectionDict::const_iterator it = d->connections.begin();
|
||||
for (; it != d->connections.end(); ++it) {
|
||||
if (it->second->isActive()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::receivedBytes(unsigned int count)
|
||||
{
|
||||
d->bytesTransferred += count;
|
||||
d->totalBytesDownloaded += count;
|
||||
}
|
||||
|
||||
unsigned int Client::transferRateBytesPerSec() const
|
||||
{
|
||||
unsigned int e = d->timeTransferSample.elapsedMSec();
|
||||
if (e > 400) {
|
||||
// too long a window, ignore
|
||||
d->timeTransferSample.stamp();
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (e < 100) { // avoid really narrow windows
|
||||
return d->lastTransferRate;
|
||||
}
|
||||
|
||||
unsigned int ratio = (d->bytesTransferred * 1000) / e;
|
||||
// run a low-pass filter
|
||||
unsigned int smoothed = ((400 - e) * d->lastTransferRate) + (e * ratio);
|
||||
smoothed /= 400;
|
||||
|
||||
d->timeTransferSample.stamp();
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = smoothed;
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
uint64_t Client::totalBytesDownloaded() const
|
||||
{
|
||||
return d->totalBytesDownloaded;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
/**
|
||||
* \file HTTPClient.hxx - simple HTTP client engine for SimHear
|
||||
*/
|
||||
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#ifndef SG_HTTP_CLIENT_HXX
|
||||
#define SG_HTTP_CLIENT_HXX
|
||||
|
||||
#include <map>
|
||||
#include <memory> // for std::auto_ptr
|
||||
#include <stdint.h> // for uint_64t
|
||||
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
#include <simgear/io/sg_netChannel.hxx>
|
||||
#include <simgear/io/HTTPFileRequest.hxx>
|
||||
#include <simgear/io/HTTPMemoryRequest.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -12,49 +36,78 @@ namespace simgear
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
// forward decls
|
||||
class Connection;
|
||||
|
||||
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
Client();
|
||||
~Client();
|
||||
|
||||
void update(int waitTimeout = 0);
|
||||
|
||||
void makeRequest(const Request_ptr& r);
|
||||
|
||||
|
||||
/**
|
||||
* Download a resource and save it to a file.
|
||||
*
|
||||
* @param url The resource to download
|
||||
* @param filename Path to the target file
|
||||
* @param data Data for POST request
|
||||
*/
|
||||
FileRequestRef save( const std::string& url,
|
||||
const std::string& filename );
|
||||
|
||||
/**
|
||||
* Request a resource and keep it in memory.
|
||||
*
|
||||
* @param url The resource to download
|
||||
*/
|
||||
MemoryRequestRef load(const std::string& url);
|
||||
|
||||
void setUserAgent(const std::string& ua);
|
||||
void setProxy(const std::string& proxy, int port, const std::string& auth = "");
|
||||
|
||||
const std::string& userAgent() const
|
||||
{ return _userAgent; }
|
||||
/**
|
||||
* Specify the maximum permitted simultaneous connections
|
||||
* (default value is 1)
|
||||
*/
|
||||
void setMaxConnections(unsigned int maxCons);
|
||||
|
||||
const std::string& userAgent() const;
|
||||
|
||||
const std::string& proxyHost() const
|
||||
{ return _proxy; }
|
||||
const std::string& proxyHost() const;
|
||||
|
||||
const std::string& proxyAuth() const
|
||||
{ return _proxyAuth; }
|
||||
const std::string& proxyAuth() const;
|
||||
|
||||
/**
|
||||
* predicate, check if at least one connection is active, with at
|
||||
* least one request active or queued.
|
||||
*/
|
||||
bool hasActiveRequests() const;
|
||||
bool hasActiveRequests() const;
|
||||
|
||||
/**
|
||||
* crude tracking of bytes-per-second transferred over the socket.
|
||||
* suitable for user feedback and rough profiling, nothing more.
|
||||
*/
|
||||
unsigned int transferRateBytesPerSec() const;
|
||||
|
||||
/**
|
||||
* total bytes downloaded by this HTTP client, for bandwidth usage
|
||||
* monitoring
|
||||
*/
|
||||
uint64_t totalBytesDownloaded() const;
|
||||
private:
|
||||
void requestFinished(Connection* con);
|
||||
|
||||
void receivedBytes(unsigned int count);
|
||||
|
||||
friend class Connection;
|
||||
friend class Request;
|
||||
|
||||
std::string _userAgent;
|
||||
std::string _proxy;
|
||||
int _proxyPort;
|
||||
std::string _proxyAuth;
|
||||
NetChannelPoller _poller;
|
||||
|
||||
// connections by host
|
||||
typedef std::map<std::string, Connection*> ConnectionDict;
|
||||
ConnectionDict _connections;
|
||||
class ClientPrivate;
|
||||
std::auto_ptr<ClientPrivate> d;
|
||||
};
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
269
simgear/io/HTTPContentDecode.cxx
Normal file
269
simgear/io/HTTPContentDecode.cxx
Normal file
@@ -0,0 +1,269 @@
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include "HTTPContentDecode.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib> // rand()
|
||||
#include <cstring> // for memset, memcpy
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/io/lowlevel.hxx> // for sgEndian stuff
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
|
||||
|
||||
// see http://www.ietf.org/rfc/rfc1952.txt for these values and
|
||||
// detailed description of the logic
|
||||
const int GZIP_HEADER_ID1 = 31;
|
||||
const int GZIP_HEADER_ID2 = 139;
|
||||
const int GZIP_HEADER_METHOD_DEFLATE = 8;
|
||||
const unsigned int GZIP_HEADER_SIZE = 10;
|
||||
const int GZIP_HEADER_FEXTRA = 1 << 2;
|
||||
const int GZIP_HEADER_FNAME = 1 << 3;
|
||||
const int GZIP_HEADER_COMMENT = 1 << 4;
|
||||
const int GZIP_HEADER_CRC = 1 << 1;
|
||||
|
||||
ContentDecoder::ContentDecoder() :
|
||||
_output(NULL),
|
||||
_zlib(NULL),
|
||||
_input(NULL),
|
||||
_inputAllocated(0),
|
||||
_inputSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
ContentDecoder::~ContentDecoder()
|
||||
{
|
||||
free(_output);
|
||||
free(_input);
|
||||
free(_zlib);
|
||||
}
|
||||
|
||||
void ContentDecoder::setEncoding(const std::string& encoding)
|
||||
{
|
||||
if (encoding == "gzip") {
|
||||
_contentDeflate = true;
|
||||
_needGZipHeader = true;
|
||||
} else if (encoding == "deflate") {
|
||||
_contentDeflate = true;
|
||||
_needGZipHeader = false;
|
||||
} else if (encoding != "identity") {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << encoding);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::reset()
|
||||
{
|
||||
_request = NULL;
|
||||
_contentDeflate = false;
|
||||
_needGZipHeader = false;
|
||||
_inputSize = 0;
|
||||
}
|
||||
|
||||
void ContentDecoder::initWithRequest(Request_ptr req)
|
||||
{
|
||||
_request = req;
|
||||
if (!_contentDeflate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_zlib) {
|
||||
_zlib = (z_stream*) malloc(sizeof(z_stream));
|
||||
}
|
||||
|
||||
memset(_zlib, 0, sizeof(z_stream));
|
||||
if (!_output) {
|
||||
_output = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
_inputSize = 0;
|
||||
// NULLs means we'll get default alloc+free methods
|
||||
// which is absolutely fine
|
||||
_zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
_zlib->next_out = _output;
|
||||
if (inflateInit2(_zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::finish()
|
||||
{
|
||||
if (_contentDeflate) {
|
||||
runDecoder();
|
||||
inflateEnd(_zlib);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::receivedBytes(const char* n, size_t s)
|
||||
{
|
||||
if (!_contentDeflate) {
|
||||
_request->processBodyBytes(n, s);
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate more space if needed (this will only happen rarely once the
|
||||
// buffer has hit something proportionate to the server's compression
|
||||
// window size)
|
||||
size_t requiredSize = _inputSize + s;
|
||||
if (requiredSize > _inputAllocated) {
|
||||
reallocateInputBuffer(requiredSize);
|
||||
}
|
||||
|
||||
// copy newly recieved bytes into the buffer
|
||||
memcpy(_input + _inputSize, n, s);
|
||||
_inputSize += s;
|
||||
|
||||
if (_needGZipHeader && !consumeGZipHeader()) {
|
||||
// still waiting on the full GZIP header, so done
|
||||
return;
|
||||
}
|
||||
|
||||
runDecoder();
|
||||
}
|
||||
|
||||
void ContentDecoder::consumeBytes(size_t consumed)
|
||||
{
|
||||
assert(_inputSize >= consumed);
|
||||
// move existing (consumed) bytes down
|
||||
if (consumed > 0) {
|
||||
size_t newSize = _inputSize - consumed;
|
||||
memmove(_input, _input + consumed, newSize);
|
||||
_inputSize = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
void ContentDecoder::reallocateInputBuffer(size_t newSize)
|
||||
{
|
||||
_input = (unsigned char*) realloc(_input, newSize);
|
||||
_inputAllocated = newSize;
|
||||
}
|
||||
|
||||
void ContentDecoder::runDecoder()
|
||||
{
|
||||
_zlib->next_in = (unsigned char*) _input;
|
||||
_zlib->avail_in = _inputSize;
|
||||
int writtenSize;
|
||||
|
||||
// loop, running zlib() inflate and sending output bytes to
|
||||
// our request body handler. Keep calling inflate until no bytes are
|
||||
// written, and ZLIB has consumed all available input
|
||||
do {
|
||||
_zlib->next_out = _output;
|
||||
_zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
int result = inflate(_zlib, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
} else if (result == Z_BUF_ERROR) {
|
||||
// transient error, fall through
|
||||
} else {
|
||||
// _error = result;
|
||||
return;
|
||||
}
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _zlib->avail_out;
|
||||
if (writtenSize > 0) {
|
||||
_request->processBodyBytes((char*) _output, writtenSize);
|
||||
}
|
||||
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
} while ((_zlib->avail_in > 0) || (writtenSize > 0));
|
||||
|
||||
// update input buffers based on what we consumed
|
||||
consumeBytes(_inputSize - _zlib->avail_in);
|
||||
}
|
||||
|
||||
bool ContentDecoder::consumeGZipHeader()
|
||||
{
|
||||
size_t avail = _inputSize;
|
||||
if (avail < GZIP_HEADER_SIZE) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
if ((_input[0] != GZIP_HEADER_ID1) ||
|
||||
(_input[1] != GZIP_HEADER_ID2) ||
|
||||
(_input[2] != GZIP_HEADER_METHOD_DEFLATE))
|
||||
{
|
||||
return false; // invalid GZip header
|
||||
}
|
||||
|
||||
char flags = _input[3];
|
||||
unsigned int gzipHeaderSize = GZIP_HEADER_SIZE;
|
||||
if (flags & GZIP_HEADER_FEXTRA) {
|
||||
gzipHeaderSize += 2;
|
||||
if (avail < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(_input + GZIP_HEADER_FEXTRA));
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( &extraHeaderBytes );
|
||||
}
|
||||
|
||||
gzipHeaderSize += extraHeaderBytes;
|
||||
if (avail < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (flags & GZIP_HEADER_FNAME) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= avail) {
|
||||
if (_input[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GZIP_HEADER_COMMENT) {
|
||||
gzipHeaderSize++;
|
||||
while (gzipHeaderSize <= avail) {
|
||||
if (_input[gzipHeaderSize-1] == 0) {
|
||||
break; // found terminating NULL character
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (flags & GZIP_HEADER_CRC) {
|
||||
gzipHeaderSize += 2;
|
||||
}
|
||||
|
||||
if (avail < gzipHeaderSize) {
|
||||
return false; // need more header bytes
|
||||
}
|
||||
|
||||
consumeBytes(gzipHeaderSize);
|
||||
_needGZipHeader = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
72
simgear/io/HTTPContentDecode.hxx
Normal file
72
simgear/io/HTTPContentDecode.hxx
Normal file
@@ -0,0 +1,72 @@
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#ifndef SG_HTTP_CONTENT_DECODER_HXX
|
||||
#define SG_HTTP_CONTENT_DECODER_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class ContentDecoder
|
||||
{
|
||||
public:
|
||||
ContentDecoder();
|
||||
~ContentDecoder();
|
||||
|
||||
void reset();
|
||||
|
||||
void initWithRequest(Request_ptr req);
|
||||
|
||||
void finish();
|
||||
|
||||
void setEncoding(const std::string& encoding);
|
||||
|
||||
void receivedBytes(const char* n, size_t s);
|
||||
|
||||
private:
|
||||
bool consumeGZipHeader();
|
||||
void runDecoder();
|
||||
|
||||
void consumeBytes(size_t consumed);
|
||||
void reallocateInputBuffer(size_t newSize);
|
||||
|
||||
Request_ptr _request;
|
||||
unsigned char* _output;
|
||||
|
||||
z_stream* _zlib;
|
||||
unsigned char* _input;
|
||||
size_t _inputAllocated, _inputSize;
|
||||
bool _contentDeflate, _needGZipHeader;
|
||||
};
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_HTTP_CONTENT_DECODER_HXX
|
||||
91
simgear/io/HTTPFileRequest.cxx
Normal file
91
simgear/io/HTTPFileRequest.cxx
Normal file
@@ -0,0 +1,91 @@
|
||||
// HTTP request writing response to a file.
|
||||
//
|
||||
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "HTTPFileRequest.hxx"
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
FileRequest::FileRequest(const std::string& url, const std::string& path):
|
||||
Request(url, "GET"),
|
||||
_filename(path)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void FileRequest::responseHeadersComplete()
|
||||
{
|
||||
Request::responseHeadersComplete();
|
||||
|
||||
if( responseCode() != 200 )
|
||||
return setFailure(responseCode(), responseReason());
|
||||
|
||||
if( !_filename.empty() )
|
||||
{
|
||||
// TODO validate path? (would require to expose fgValidatePath somehow to
|
||||
// simgear)
|
||||
SGPath path(_filename);
|
||||
path.create_dir(0755);
|
||||
|
||||
_file.open(_filename.c_str(), std::ios::binary | std::ios::trunc);
|
||||
}
|
||||
|
||||
if( !_file )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_WARN,
|
||||
"HTTP::FileRequest: failed to open file '" << _filename << "'"
|
||||
);
|
||||
|
||||
abort("Failed to open file.");
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void FileRequest::gotBodyData(const char* s, int n)
|
||||
{
|
||||
if( !_file )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_DEBUG,
|
||||
"HTTP::FileRequest: received data for closed file '" << _filename << "'"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_file.write(s, n);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void FileRequest::onAlways()
|
||||
{
|
||||
_file.close();
|
||||
}
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
56
simgear/io/HTTPFileRequest.hxx
Normal file
56
simgear/io/HTTPFileRequest.hxx
Normal file
@@ -0,0 +1,56 @@
|
||||
///@file HTTP request writing response to a file.
|
||||
//
|
||||
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_HTTP_FILEREQUEST_HXX_
|
||||
#define SG_HTTP_FILEREQUEST_HXX_
|
||||
|
||||
#include "HTTPRequest.hxx"
|
||||
#include <fstream>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class FileRequest:
|
||||
public Request
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url Adress to download from
|
||||
* @param path Path to file for saving response
|
||||
*/
|
||||
FileRequest(const std::string& url, const std::string& path);
|
||||
|
||||
protected:
|
||||
std::string _filename;
|
||||
std::ofstream _file;
|
||||
|
||||
virtual void responseHeadersComplete();
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
virtual void onAlways();
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<FileRequest> FileRequestRef;
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_HTTP_FILEREQUEST_HXX_ */
|
||||
55
simgear/io/HTTPMemoryRequest.cxx
Normal file
55
simgear/io/HTTPMemoryRequest.cxx
Normal file
@@ -0,0 +1,55 @@
|
||||
// HTTP request keeping response in memory.
|
||||
//
|
||||
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "HTTPMemoryRequest.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
MemoryRequest::MemoryRequest(const std::string& url):
|
||||
Request(url, "GET")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
const std::string& MemoryRequest::responseBody() const
|
||||
{
|
||||
return _response;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void MemoryRequest::responseHeadersComplete()
|
||||
{
|
||||
Request::responseHeadersComplete();
|
||||
|
||||
if( responseLength() )
|
||||
_response.reserve( responseLength() );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void MemoryRequest::gotBodyData(const char* s, int n)
|
||||
{
|
||||
_response.append(s, n);
|
||||
}
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
58
simgear/io/HTTPMemoryRequest.hxx
Normal file
58
simgear/io/HTTPMemoryRequest.hxx
Normal file
@@ -0,0 +1,58 @@
|
||||
///@file HTTP request keeping response in memory.
|
||||
//
|
||||
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_HTTP_MEMORYREQUEST_HXX_
|
||||
#define SG_HTTP_MEMORYREQUEST_HXX_
|
||||
|
||||
#include "HTTPRequest.hxx"
|
||||
#include <fstream>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class MemoryRequest:
|
||||
public Request
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url Adress to download from
|
||||
*/
|
||||
MemoryRequest(const std::string& url);
|
||||
|
||||
/**
|
||||
* Body contents of server response.
|
||||
*/
|
||||
const std::string& responseBody() const;
|
||||
|
||||
protected:
|
||||
std::string _response;
|
||||
|
||||
virtual void responseHeadersComplete();
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<MemoryRequest> MemoryRequestRef;
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_HTTP_MEMORYREQUEST_HXX_ */
|
||||
@@ -1,104 +1,190 @@
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
extern const int DEFAULT_HTTP_PORT;
|
||||
|
||||
Request::Request(const string& url, const string method) :
|
||||
_method(method),
|
||||
_url(url),
|
||||
_responseVersion(HTTP_VERSION_UNKNOWN),
|
||||
_responseStatus(0),
|
||||
_responseLength(0),
|
||||
_receivedBodyBytes(0),
|
||||
_willClose(false)
|
||||
//------------------------------------------------------------------------------
|
||||
Request::Request(const std::string& url, const std::string method):
|
||||
_method(method),
|
||||
_url(url),
|
||||
_responseVersion(HTTP_VERSION_UNKNOWN),
|
||||
_responseStatus(0),
|
||||
_responseLength(0),
|
||||
_receivedBodyBytes(0),
|
||||
_ready_state(UNSENT),
|
||||
_willClose(false)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Request::~Request()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Request::setUrl(const string& url)
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::done(const Callback& cb)
|
||||
{
|
||||
_url = url;
|
||||
if( _ready_state == DONE )
|
||||
cb(this);
|
||||
else
|
||||
_cb_done = cb;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
string_list Request::requestHeaders() const
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::fail(const Callback& cb)
|
||||
{
|
||||
string_list r;
|
||||
return r;
|
||||
if( _ready_state == FAILED )
|
||||
cb(this);
|
||||
else
|
||||
_cb_fail = cb;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
string Request::header(const std::string& name) const
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::always(const Callback& cb)
|
||||
{
|
||||
return string();
|
||||
if( isComplete() )
|
||||
cb(this);
|
||||
else
|
||||
_cb_always = cb;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setBodyData( const std::string& data,
|
||||
const std::string& type )
|
||||
{
|
||||
_request_data = data;
|
||||
_request_media_type = type;
|
||||
|
||||
if( !data.empty() && _method == "GET" )
|
||||
_method = "POST";
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Request::setBodyData(const SGPropertyNode* data)
|
||||
{
|
||||
if( !data )
|
||||
setBodyData("");
|
||||
|
||||
std::stringstream buf;
|
||||
writeProperties(buf, data, true);
|
||||
|
||||
setBodyData(buf.str(), "application/xml");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setUrl(const std::string& url)
|
||||
{
|
||||
_url = url;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::requestStart()
|
||||
{
|
||||
|
||||
setReadyState(OPENED);
|
||||
}
|
||||
|
||||
void Request::responseStart(const string& r)
|
||||
//------------------------------------------------------------------------------
|
||||
Request::HTTPVersion decodeHTTPVersion(const std::string& v)
|
||||
{
|
||||
if( v == "HTTP/1.1" ) return Request::HTTP_1_1;
|
||||
if( v == "HTTP/1.0" ) return Request::HTTP_1_0;
|
||||
if( strutils::starts_with(v, "HTTP/0.") ) return Request::HTTP_0_x;
|
||||
return Request::HTTP_VERSION_UNKNOWN;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseStart(const std::string& r)
|
||||
{
|
||||
const int maxSplit = 2; // HTTP/1.1 nnn reason-string
|
||||
string_list parts = strutils::split(r, NULL, maxSplit);
|
||||
if (parts.size() != 3) {
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP::Request: malformed response start:" << r);
|
||||
setFailure(400, "malformed HTTP response header");
|
||||
return;
|
||||
throw sg_io_exception("bad HTTP response");
|
||||
}
|
||||
|
||||
_responseVersion = decodeVersion(parts[0]);
|
||||
_responseVersion = decodeHTTPVersion(parts[0]);
|
||||
_responseStatus = strutils::to_int(parts[1]);
|
||||
_responseReason = parts[2];
|
||||
}
|
||||
|
||||
void Request::responseHeader(const string& key, const string& value)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseHeader(const std::string& key, const std::string& value)
|
||||
{
|
||||
if (key == "connection") {
|
||||
_willClose = (value.find("close") != string::npos);
|
||||
}
|
||||
|
||||
_responseHeaders[key] = value;
|
||||
if( key == "connection" )
|
||||
_willClose = (value.find("close") != std::string::npos);
|
||||
|
||||
_responseHeaders[key] = value;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseHeadersComplete()
|
||||
{
|
||||
// no op
|
||||
}
|
||||
|
||||
void Request::processBodyBytes(const char* s, int n)
|
||||
{
|
||||
_receivedBodyBytes += n;
|
||||
gotBodyData(s, n);
|
||||
}
|
||||
|
||||
void Request::gotBodyData(const char* s, int n)
|
||||
{
|
||||
|
||||
setReadyState(HEADERS_RECEIVED);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::responseComplete()
|
||||
{
|
||||
|
||||
if( !isComplete() )
|
||||
setReadyState(DONE);
|
||||
}
|
||||
|
||||
string Request::scheme() const
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::gotBodyData(const char* s, int n)
|
||||
{
|
||||
setReadyState(LOADING);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onDone()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onFail()
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_INFO,
|
||||
"request failed:" << url() << " : "
|
||||
<< responseCode() << "/" << responseReason()
|
||||
);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onAlways()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::processBodyBytes(const char* s, int n)
|
||||
{
|
||||
_receivedBodyBytes += n;
|
||||
gotBodyData(s, n);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::scheme() const
|
||||
{
|
||||
int firstColon = url().find(":");
|
||||
if (firstColon > 0) {
|
||||
@@ -107,10 +193,11 @@ string Request::scheme() const
|
||||
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
string Request::path() const
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::path() const
|
||||
{
|
||||
string u(url());
|
||||
std::string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
@@ -132,10 +219,10 @@ string Request::path() const
|
||||
return u.substr(hostEnd, query - hostEnd);
|
||||
}
|
||||
|
||||
|
||||
string Request::query() const
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::query() const
|
||||
{
|
||||
string u(url());
|
||||
std::string u(url());
|
||||
int query = u.find('?');
|
||||
if (query < 0) {
|
||||
return ""; //no query string found
|
||||
@@ -144,104 +231,159 @@ string Request::query() const
|
||||
return u.substr(query); //includes question mark
|
||||
}
|
||||
|
||||
|
||||
|
||||
string Request::host() const
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::host() const
|
||||
{
|
||||
string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return hp.substr(0, colonPos); // trim off the colon and port
|
||||
} else {
|
||||
return hp; // no port specifier
|
||||
}
|
||||
std::string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return hp.substr(0, colonPos); // trim off the colon and port
|
||||
} else {
|
||||
return hp; // no port specifier
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
unsigned short Request::port() const
|
||||
{
|
||||
string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
|
||||
} else {
|
||||
return DEFAULT_HTTP_PORT;
|
||||
}
|
||||
std::string hp(hostAndPort());
|
||||
int colonPos = hp.find(':');
|
||||
if (colonPos >= 0) {
|
||||
return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
|
||||
} else {
|
||||
return DEFAULT_HTTP_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
string Request::hostAndPort() const
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::hostAndPort() const
|
||||
{
|
||||
string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
int hostEnd = u.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) { // all remainder of URL is host
|
||||
return u.substr(schemeEnd + 3);
|
||||
}
|
||||
|
||||
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
||||
std::string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
int hostEnd = u.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) { // all remainder of URL is host
|
||||
return u.substr(schemeEnd + 3);
|
||||
}
|
||||
|
||||
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setResponseLength(unsigned int l)
|
||||
{
|
||||
_responseLength = l;
|
||||
_responseLength = l;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
unsigned int Request::responseLength() const
|
||||
{
|
||||
// if the server didn't supply a content length, use the number
|
||||
// of bytes we actually received (so far)
|
||||
if ((_responseLength == 0) && (_receivedBodyBytes > 0)) {
|
||||
return _receivedBodyBytes;
|
||||
}
|
||||
|
||||
return _responseLength;
|
||||
// if the server didn't supply a content length, use the number
|
||||
// of bytes we actually received (so far)
|
||||
if( (_responseLength == 0) && (_receivedBodyBytes > 0) )
|
||||
return _receivedBodyBytes;
|
||||
|
||||
return _responseLength;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setFailure(int code, const std::string& reason)
|
||||
{
|
||||
_responseStatus = code;
|
||||
_responseReason = reason;
|
||||
failed();
|
||||
_responseStatus = code;
|
||||
_responseReason = reason;
|
||||
setReadyState(FAILED);
|
||||
}
|
||||
|
||||
void Request::failed()
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setReadyState(ReadyState state)
|
||||
{
|
||||
// no-op in base class
|
||||
SG_LOG(SG_IO, SG_INFO, "request failed:" << url());
|
||||
_ready_state = state;
|
||||
if( state == DONE )
|
||||
{
|
||||
// Finish C++ part of request to ensure everything is finished (for example
|
||||
// files and streams are closed) before calling any callback (possibly using
|
||||
// such files)
|
||||
onDone();
|
||||
onAlways();
|
||||
|
||||
if( _cb_done )
|
||||
_cb_done(this);
|
||||
}
|
||||
else if( state == FAILED )
|
||||
{
|
||||
onFail();
|
||||
onAlways();
|
||||
|
||||
if( _cb_fail )
|
||||
_cb_fail(this);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if( _cb_always )
|
||||
_cb_always(this);
|
||||
}
|
||||
|
||||
Request::HTTPVersion Request::decodeVersion(const string& v)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::abort()
|
||||
{
|
||||
if (v == "HTTP/1.1") return HTTP_1_1;
|
||||
if (v == "HTTP/1.0") return HTTP_1_0;
|
||||
if (strutils::starts_with(v, "HTTP/0.")) return HTTP_0_x;
|
||||
return HTTP_VERSION_UNKNOWN;
|
||||
abort("Request aborted.");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void Request::abort(const std::string& reason)
|
||||
{
|
||||
if( isComplete() )
|
||||
return;
|
||||
|
||||
setFailure(-1, reason);
|
||||
_willClose = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::closeAfterComplete() const
|
||||
{
|
||||
// for non HTTP/1.1 connections, assume server closes
|
||||
return _willClose || (_responseVersion != HTTP_1_1);
|
||||
}
|
||||
|
||||
int Request::requestBodyLength() const
|
||||
{
|
||||
return -1;
|
||||
// for non HTTP/1.1 connections, assume server closes
|
||||
return _willClose || (_responseVersion != HTTP_1_1);
|
||||
}
|
||||
|
||||
std::string Request::requestBodyType() const
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::isComplete() const
|
||||
{
|
||||
return "text/plain";
|
||||
return _ready_state == DONE || _ready_state == FAILED;
|
||||
}
|
||||
|
||||
int Request::getBodyData(char*, int maxCount) const
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::hasBodyData() const
|
||||
{
|
||||
return 0;
|
||||
return !_request_media_type.empty();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string Request::bodyType() const
|
||||
{
|
||||
return _request_media_type;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
size_t Request::bodyLength() const
|
||||
{
|
||||
return _request_data.length();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
size_t Request::getBodyData(char* s, size_t offset, size_t max_count) const
|
||||
{
|
||||
size_t bytes_available = _request_data.size() - offset;
|
||||
size_t bytes_to_read = std::min(bytes_available, max_count);
|
||||
|
||||
memcpy(s, _request_data.data() + offset, bytes_to_read);
|
||||
|
||||
return bytes_to_read;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -3,21 +3,85 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <simgear/structure/map.hxx>
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
#include <simgear/math/sg_types.hxx>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
|
||||
class SGPropertyNode;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class Request : public SGReferenced
|
||||
class Request:
|
||||
public SGReferenced
|
||||
{
|
||||
public:
|
||||
typedef boost::function<void(Request*)> Callback;
|
||||
|
||||
enum ReadyState
|
||||
{
|
||||
UNSENT = 0,
|
||||
OPENED,
|
||||
HEADERS_RECEIVED,
|
||||
LOADING,
|
||||
DONE,
|
||||
FAILED
|
||||
};
|
||||
|
||||
virtual ~Request();
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
StringMap& requestHeaders() { return _request_headers; }
|
||||
const StringMap& requestHeaders() const { return _request_headers; }
|
||||
std::string& requestHeader(const std::string& key)
|
||||
{ return _request_headers[key]; }
|
||||
const std::string requestHeader(const std::string& key) const
|
||||
{ return _request_headers.get(key); }
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the request successfully completes.
|
||||
*
|
||||
* @note If the request is already complete, the handler is called
|
||||
* immediately.
|
||||
*/
|
||||
Request* done(const Callback& cb);
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the request completes or aborts with an
|
||||
* error.
|
||||
*
|
||||
* @note If the request has already failed, the handler is called
|
||||
* immediately.
|
||||
*/
|
||||
Request* fail(const Callback& cb);
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the request either successfully
|
||||
* completes or fails.
|
||||
*
|
||||
* @note If the request is already complete or has already failed, the
|
||||
* handler is called immediately.
|
||||
*/
|
||||
Request* always(const Callback& cb);
|
||||
|
||||
/**
|
||||
* Set the data for the body of the request. The request is automatically
|
||||
* send using the POST method.
|
||||
*
|
||||
* @param data Body data
|
||||
* @param type Media Type (aka MIME) of the body data
|
||||
*/
|
||||
void setBodyData( const std::string& data,
|
||||
const std::string& type = "text/plain" );
|
||||
void setBodyData( const SGPropertyNode* data );
|
||||
|
||||
virtual void setUrl(const std::string& url);
|
||||
|
||||
virtual std::string method() const
|
||||
@@ -32,8 +96,8 @@ public:
|
||||
virtual unsigned short port() const;
|
||||
virtual std::string query() const;
|
||||
|
||||
virtual string_list requestHeaders() const;
|
||||
virtual std::string header(const std::string& name) const;
|
||||
StringMap const& responseHeaders() const
|
||||
{ return _responseHeaders; }
|
||||
|
||||
virtual int responseCode() const
|
||||
{ return _responseStatus; }
|
||||
@@ -41,26 +105,30 @@ public:
|
||||
virtual std::string responseReason() const
|
||||
{ return _responseReason; }
|
||||
|
||||
void setResponseLength(unsigned int l);
|
||||
void setResponseLength(unsigned int l);
|
||||
virtual unsigned int responseLength() const;
|
||||
|
||||
/**
|
||||
* Query the size of the request body. -1 (the default value) means no
|
||||
* request body
|
||||
* Check if request contains body data.
|
||||
*/
|
||||
virtual int requestBodyLength() const;
|
||||
virtual bool hasBodyData() const;
|
||||
|
||||
/**
|
||||
* Retrieve the request body content type.
|
||||
*/
|
||||
virtual std::string bodyType() const;
|
||||
|
||||
/**
|
||||
* Retrieve the size of the request body.
|
||||
*/
|
||||
virtual size_t bodyLength() const;
|
||||
|
||||
/**
|
||||
* Retrieve the body data bytes. Will be passed the maximum body bytes
|
||||
* to return in the buffer, and must return the actual number
|
||||
* of bytes written.
|
||||
*/
|
||||
virtual int getBodyData(char* s, int count) const;
|
||||
|
||||
/**
|
||||
* retrieve the request body content type. Default is text/plain
|
||||
*/
|
||||
virtual std::string requestBodyType() const;
|
||||
virtual size_t getBodyData(char* s, size_t offset, size_t max_count) const;
|
||||
|
||||
/**
|
||||
* running total of body bytes received so far. Can be used
|
||||
@@ -80,9 +148,21 @@ public:
|
||||
HTTPVersion responseVersion() const
|
||||
{ return _responseVersion; }
|
||||
|
||||
static HTTPVersion decodeVersion(const std::string& v);
|
||||
|
||||
ReadyState readyState() const { return _ready_state; }
|
||||
|
||||
/**
|
||||
* Request aborting this request.
|
||||
*/
|
||||
void abort();
|
||||
|
||||
/**
|
||||
* Request aborting this request and specify the reported reaseon for it.
|
||||
*/
|
||||
void abort(const std::string& reason);
|
||||
|
||||
bool closeAfterComplete() const;
|
||||
bool isComplete() const;
|
||||
|
||||
protected:
|
||||
Request(const std::string& url, const std::string method = "GET");
|
||||
|
||||
@@ -91,33 +171,49 @@ protected:
|
||||
virtual void responseHeader(const std::string& key, const std::string& value);
|
||||
virtual void responseHeadersComplete();
|
||||
virtual void responseComplete();
|
||||
virtual void failed();
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
|
||||
virtual void onDone();
|
||||
virtual void onFail();
|
||||
virtual void onAlways();
|
||||
|
||||
void setFailure(int code, const std::string& reason);
|
||||
|
||||
private:
|
||||
friend class Client;
|
||||
friend class Connection;
|
||||
friend class ContentDecoder;
|
||||
|
||||
void processBodyBytes(const char* s, int n);
|
||||
void setFailure(int code, const std::string& reason);
|
||||
Request(const Request&); // = delete;
|
||||
Request& operator=(const Request&); // = delete;
|
||||
|
||||
std::string _method;
|
||||
std::string _url;
|
||||
HTTPVersion _responseVersion;
|
||||
int _responseStatus;
|
||||
std::string _responseReason;
|
||||
unsigned int _responseLength;
|
||||
unsigned int _receivedBodyBytes;
|
||||
bool _willClose;
|
||||
|
||||
typedef std::map<std::string, std::string> HeaderDict;
|
||||
HeaderDict _responseHeaders;
|
||||
void processBodyBytes(const char* s, int n);
|
||||
void setReadyState(ReadyState state);
|
||||
|
||||
std::string _method;
|
||||
std::string _url;
|
||||
StringMap _request_headers;
|
||||
std::string _request_data;
|
||||
std::string _request_media_type;
|
||||
|
||||
HTTPVersion _responseVersion;
|
||||
int _responseStatus;
|
||||
std::string _responseReason;
|
||||
StringMap _responseHeaders;
|
||||
unsigned int _responseLength;
|
||||
unsigned int _receivedBodyBytes;
|
||||
|
||||
Callback _cb_done,
|
||||
_cb_fail,
|
||||
_cb_always;
|
||||
|
||||
ReadyState _ready_state;
|
||||
bool _willClose;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<Request> Request_ptr;
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_HTTP_REQUEST_HXX
|
||||
|
||||
|
||||
@@ -28,7 +28,12 @@ typedef std::map<std::string, DAVResource*> DAVResourceMap;
|
||||
|
||||
const char* DAV_CACHE_NAME = ".terrasync_cache";
|
||||
const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4";
|
||||
const unsigned int MAX_UPDATE_REPORT_DEPTH = 3;
|
||||
|
||||
// important: with the Google servers, setting this higher than '1' causes
|
||||
// server internal errors (500, the connection is closed). In other words we
|
||||
// can only specify update report items one level deep at most and no more.
|
||||
// (the root and its direct children, not NOT grand-children)
|
||||
const unsigned int MAX_UPDATE_REPORT_DEPTH = 1;
|
||||
|
||||
enum LineState
|
||||
{
|
||||
@@ -90,11 +95,15 @@ void SVNDirectory::parseCache()
|
||||
char versionName[128];
|
||||
LineState lineState = LINESTATE_HREF;
|
||||
std::ifstream file(p.c_str());
|
||||
if (!file.is_open()) {
|
||||
SG_LOG(SG_IO, SG_WARN, "unable to open cache file for reading:" << p);
|
||||
return;
|
||||
}
|
||||
bool doneSelf = false;
|
||||
|
||||
file.getline(href, 1024);
|
||||
if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "invalid cache file:" << p.str());
|
||||
SG_LOG(SG_IO, SG_WARN, "invalid cache file [missing header token]:" << p << " '" << href << "'");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,8 +137,11 @@ void SVNDirectory::parseCache()
|
||||
doneSelf = true;
|
||||
} else {
|
||||
DAVResource* child = addChildDirectory(hrefPtr)->collection();
|
||||
child->setVersionName(versionName);
|
||||
}
|
||||
string s = strutils::strip(versionName);
|
||||
if (!s.empty()) {
|
||||
child->setVersionName(versionName);
|
||||
}
|
||||
} // of done self test
|
||||
} // of line-state switching
|
||||
} // of file get-line loop
|
||||
}
|
||||
@@ -142,7 +154,7 @@ void SVNDirectory::writeCache()
|
||||
d.create(0755);
|
||||
}
|
||||
|
||||
p.append(DAV_CACHE_NAME);
|
||||
p.append(string(DAV_CACHE_NAME) + ".new");
|
||||
|
||||
std::ofstream file(p.c_str(), std::ios::trunc);
|
||||
// first, cache file version header
|
||||
@@ -159,6 +171,13 @@ void SVNDirectory::writeCache()
|
||||
file << child->name() << '\n' << child->versionName() << "\n";
|
||||
}
|
||||
} // of child iteration
|
||||
|
||||
file.close();
|
||||
|
||||
// approximately atomic delete + rename operation
|
||||
SGPath cacheName(localPath);
|
||||
cacheName.append(DAV_CACHE_NAME);
|
||||
p.rename(cacheName);
|
||||
}
|
||||
|
||||
void SVNDirectory::setBaseUrl(const string& url)
|
||||
@@ -210,6 +229,7 @@ SVNDirectory::addChildDirectory(const std::string& dirName)
|
||||
|
||||
DAVCollection* childCol = dav->createChildCollection(dirName);
|
||||
SVNDirectory* child = new SVNDirectory(this, childCol);
|
||||
childCol->setVersionName(child->cachedRevision());
|
||||
_children.push_back(child);
|
||||
writeCache();
|
||||
return child;
|
||||
@@ -224,8 +244,6 @@ void SVNDirectory::deleteChildByName(const std::string& nm)
|
||||
}
|
||||
|
||||
SGPath path = fsDir().file(nm);
|
||||
dav->removeChild(child);
|
||||
delete child;
|
||||
|
||||
if (child->isCollection()) {
|
||||
Dir d(path);
|
||||
@@ -241,15 +259,13 @@ void SVNDirectory::deleteChildByName(const std::string& nm)
|
||||
} else {
|
||||
path.remove();
|
||||
}
|
||||
|
||||
|
||||
dav->removeChild(child);
|
||||
delete child;
|
||||
|
||||
writeCache();
|
||||
}
|
||||
|
||||
void SVNDirectory::requestFailed(HTTP::Request *req)
|
||||
{
|
||||
SG_LOG(SG_IO, SG_WARN, "Request failed for:" << req->url());
|
||||
}
|
||||
|
||||
bool SVNDirectory::isDoingSync() const
|
||||
{
|
||||
if (_doingUpdateReport) {
|
||||
@@ -303,7 +319,7 @@ void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
|
||||
|
||||
Dir d(localPath);
|
||||
if (depth >= MAX_UPDATE_REPORT_DEPTH) {
|
||||
std::cerr << localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning" << std::endl;
|
||||
SG_LOG(SG_IO, SG_INFO, localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning");
|
||||
d.removeChildren();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,11 +51,7 @@ public:
|
||||
|
||||
// init from a collection
|
||||
SVNDirectory(SVNDirectory* pr, DAVCollection* col);
|
||||
|
||||
// void update();
|
||||
// void gotResource(HTTP::Request* get, const std::string& etag);
|
||||
void requestFailed(HTTP::Request* req);
|
||||
|
||||
|
||||
void beginUpdateReport();
|
||||
void updateReportComplete();
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include "SVNReportParser.hxx"
|
||||
|
||||
#include <iostream>
|
||||
@@ -30,11 +34,16 @@
|
||||
#include "simgear/misc/sg_path.hxx"
|
||||
#include "simgear/misc/sg_dir.hxx"
|
||||
#include "simgear/debug/logstream.hxx"
|
||||
#include "simgear/xml/xmlparse.h"
|
||||
#include "simgear/xml/easyxml.hxx"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include "simgear/package/md5.h"
|
||||
|
||||
#ifdef SYSTEM_EXPAT
|
||||
# include <expat.h>
|
||||
#else
|
||||
# include "sg_expat.h"
|
||||
#endif
|
||||
|
||||
#include "SVNDirectory.hxx"
|
||||
#include "SVNRepository.hxx"
|
||||
#include "DAVMultiStatus.hxx"
|
||||
@@ -97,12 +106,13 @@ namespace {
|
||||
const char* SVN_ADD_FILE_TAG = SVN_NS "add-file";
|
||||
const char* SVN_TXDELTA_TAG = SVN_NS "txdelta";
|
||||
const char* SVN_SET_PROP_TAG = SVN_NS "set-prop";
|
||||
const char* SVN_PROP_TAG = SVN_NS "prop";
|
||||
const char* SVN_DELETE_ENTRY_TAG = SVN_NS "delete-entry";
|
||||
|
||||
const char* SVN_DAV_MD5_CHECKSUM = SUBVERSION_DAV_NS ":md5-checksum";
|
||||
|
||||
const char* DAV_HREF_TAG = DAV_NS "href";
|
||||
const char* DAV_CHECKED_IN_TAG = SVN_NS "checked-in";
|
||||
const char* DAV_CHECKED_IN_TAG = DAV_NS "checked-in";
|
||||
|
||||
|
||||
const int svn_txdelta_source = 0;
|
||||
@@ -156,7 +166,7 @@ namespace {
|
||||
_ptr = p;
|
||||
}
|
||||
|
||||
bool apply(std::vector<char>& output, std::istream& source)
|
||||
bool apply(std::vector<unsigned char>& output, std::istream& source)
|
||||
{
|
||||
unsigned char* pEnd = _ptr + instructionLength;
|
||||
unsigned char* newData = pEnd;
|
||||
@@ -164,7 +174,7 @@ namespace {
|
||||
while (_ptr < pEnd) {
|
||||
int op = ((*_ptr >> 6) & 0x3);
|
||||
if (op >= 3) {
|
||||
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: bad opcode:" << op);
|
||||
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: bad opcode:" << op);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -186,12 +196,14 @@ namespace {
|
||||
}
|
||||
|
||||
if (op == svn_txdelta_target) {
|
||||
while (length > 0) {
|
||||
output.push_back(output[offset++]);
|
||||
--length;
|
||||
}
|
||||
// this is inefficent, but ranges can overlap.
|
||||
while (length > 0) {
|
||||
output.push_back(output[offset++]);
|
||||
--length;
|
||||
}
|
||||
} else if (op == svn_txdelta_new) {
|
||||
output.insert(output.end(), newData, newData + length);
|
||||
newData += length;
|
||||
} else if (op == svn_txdelta_source) {
|
||||
source.seekg(offset);
|
||||
char* sourceBuf = (char*) malloc(length);
|
||||
@@ -199,6 +211,9 @@ namespace {
|
||||
source.read(sourceBuf, length);
|
||||
output.insert(output.end(), sourceBuf, sourceBuf + length);
|
||||
free(sourceBuf);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "bad opcode logic");
|
||||
return false;
|
||||
}
|
||||
} // of instruction loop
|
||||
|
||||
@@ -260,12 +275,12 @@ public:
|
||||
string fileName(attrs.getValue("name"));
|
||||
SGPath filePath(Dir(currentPath).file(fileName));
|
||||
currentPath = filePath;
|
||||
|
||||
DAVResource* res = currentDir->collection()->childWithName(fileName);
|
||||
if (!res || !filePath.exists()) {
|
||||
// set error condition
|
||||
|
||||
if (!filePath.exists()) {
|
||||
fail(SVNRepository::SVN_ERROR_FILE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
inFile = true;
|
||||
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
|
||||
string dirName(attrs.getValue("name"));
|
||||
@@ -293,10 +308,12 @@ public:
|
||||
} else if (!strcmp(name, SVN_DELETE_ENTRY_TAG)) {
|
||||
string entryName(attrs.getValue("name"));
|
||||
deleteEntry(entryName);
|
||||
} else if (!strcmp(name, DAV_CHECKED_IN_TAG) || !strcmp(name, DAV_HREF_TAG)) {
|
||||
} else if (!strcmp(name, DAV_CHECKED_IN_TAG) ||
|
||||
!strcmp(name, DAV_HREF_TAG) ||
|
||||
!strcmp(name, SVN_PROP_TAG)) {
|
||||
// don't warn on these ones
|
||||
} else {
|
||||
//std::cerr << "unhandled element:" << name << std::endl;
|
||||
//SG_LOG(SG_IO, SG_WARN, "SVNReportParser: unhandled tag:" << name);
|
||||
}
|
||||
} // of startElement
|
||||
|
||||
@@ -322,11 +339,12 @@ public:
|
||||
|
||||
bool decodeTextDelta(const SGPath& outputPath)
|
||||
{
|
||||
string decoded = strutils::decodeBase64(txDeltaData);
|
||||
std::vector<unsigned char> output, decoded;
|
||||
strutils::decodeBase64(txDeltaData, decoded);
|
||||
size_t bytesToDecode = decoded.size();
|
||||
std::vector<char> output;
|
||||
unsigned char* p = (unsigned char*) decoded.data();
|
||||
if (memcmp(decoded.data(), "SVN\0", DELTA_HEADER_SIZE) != 0) {
|
||||
|
||||
unsigned char* p = decoded.data();
|
||||
if (memcmp(p, "SVN\0", DELTA_HEADER_SIZE) != 0) {
|
||||
return false; // bad header
|
||||
}
|
||||
|
||||
@@ -336,6 +354,11 @@ public:
|
||||
source.open(outputPath.c_str(), std::ios::in | std::ios::binary);
|
||||
|
||||
while (bytesToDecode > 0) {
|
||||
if (!SVNDeltaWindow::isWindowComplete(p, bytesToDecode)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN txdelta broken window");
|
||||
return false;
|
||||
}
|
||||
|
||||
SVNDeltaWindow window(p);
|
||||
assert(bytesToDecode >= window.size());
|
||||
window.apply(output, source);
|
||||
@@ -344,18 +367,19 @@ public:
|
||||
}
|
||||
|
||||
source.close();
|
||||
|
||||
std::ofstream f;
|
||||
f.open(outputPath.c_str(),
|
||||
std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
f.write(output.data(), output.size());
|
||||
|
||||
f.write((char*) output.data(), output.size());
|
||||
|
||||
// compute MD5 while we have the file in memory
|
||||
MD5_CTX md5;
|
||||
memset(&md5, 0, sizeof(MD5_CTX));
|
||||
MD5Init(&md5);
|
||||
MD5Update(&md5, (unsigned char*) output.data(), output.size());
|
||||
MD5Final(&md5);
|
||||
decodedFileMd5 = strutils::encodeHex(md5.digest, 16);
|
||||
memset(&md5Context, 0, sizeof(SG_MD5_CTX));
|
||||
SG_MD5Init(&md5Context);
|
||||
SG_MD5Update(&md5Context, (unsigned char*) output.data(), output.size());
|
||||
SG_MD5Final(&md5Context);
|
||||
decodedFileMd5 = strutils::encodeHex(md5Context.digest, 16);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -367,16 +391,15 @@ public:
|
||||
|
||||
assert(tagStack.back() == name);
|
||||
tagStack.pop_back();
|
||||
|
||||
if (!strcmp(name, SVN_TXDELTA_TAG)) {
|
||||
if (!decodeTextDelta(currentPath)) {
|
||||
fail(SVNRepository::SVN_ERROR_TXDELTA);
|
||||
fail(SVNRepository::SVN_ERROR_TXDELTA);
|
||||
}
|
||||
} else if (!strcmp(name, SVN_ADD_FILE_TAG)) {
|
||||
finishFile(currentDir->addChildFile(currentPath.file()));
|
||||
finishFile(currentPath);
|
||||
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
|
||||
DAVResource* res = currentDir->collection()->childWithName(currentPath.file());
|
||||
assert(res);
|
||||
finishFile(res);
|
||||
finishFile(currentPath);
|
||||
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
|
||||
// pop directory
|
||||
currentPath = currentPath.dir();
|
||||
@@ -399,23 +422,21 @@ public:
|
||||
fail(SVNRepository::SVN_ERROR_CHECKSUM);
|
||||
}
|
||||
} else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) {
|
||||
currentDir->updateReportComplete();
|
||||
if (currentDir->parent()) {
|
||||
// pop the collection stack
|
||||
currentDir = currentDir->parent();
|
||||
}
|
||||
|
||||
currentDir->updateReportComplete();
|
||||
currentPath = currentDir->fsPath();
|
||||
} else {
|
||||
// std::cout << "element:" << name;
|
||||
}
|
||||
}
|
||||
|
||||
void finishFile(DAVResource* res)
|
||||
void finishFile(const SGPath& path)
|
||||
{
|
||||
res->setVersionName(currentVersionName);
|
||||
res->setMD5(md5Sum);
|
||||
currentPath = currentPath.dir();
|
||||
currentPath = path.dir();
|
||||
inFile = false;
|
||||
}
|
||||
|
||||
@@ -426,11 +447,11 @@ public:
|
||||
}
|
||||
|
||||
if (tagStack.back() == SVN_SET_PROP_TAG) {
|
||||
setPropValue += string(s, length);
|
||||
setPropValue.append(s, length);
|
||||
} else if (tagStack.back() == SVN_TXDELTA_TAG) {
|
||||
txDeltaData += string(s, length);
|
||||
txDeltaData.append(s, length);
|
||||
} else if (tagStack.back() == SVN_DAV_MD5_CHECKSUM) {
|
||||
md5Sum += string(s, length);
|
||||
md5Sum.append(s, length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +488,7 @@ public:
|
||||
bool inFile;
|
||||
|
||||
unsigned int revision;
|
||||
SG_MD5_CTX md5Context;
|
||||
string md5Sum, decodedFileMd5;
|
||||
std::string setPropName, setPropValue;
|
||||
};
|
||||
@@ -538,7 +560,7 @@ SVNReportParser::innerParseXML(const char* data, int size)
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
_d->parserInited = false;
|
||||
}
|
||||
|
||||
|
||||
return _d->status;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ public:
|
||||
|
||||
void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
|
||||
{
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN: failed to update from:" << req->url());
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN: failed to update from:" << req->url()
|
||||
<< "\n(repository:" << p->baseUrl() << ")");
|
||||
isUpdating = false;
|
||||
status = err;
|
||||
}
|
||||
@@ -119,40 +120,9 @@ namespace { // anonmouse
|
||||
Request(repo->baseUrl, "PROPFIND"),
|
||||
_repo(repo)
|
||||
{
|
||||
}
|
||||
|
||||
virtual string_list requestHeaders() const
|
||||
{
|
||||
string_list r;
|
||||
r.push_back("Depth");
|
||||
return r;
|
||||
}
|
||||
|
||||
virtual string header(const string& name) const
|
||||
{
|
||||
if (name == "Depth") {
|
||||
return "0";
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
|
||||
virtual string requestBodyType() const
|
||||
{
|
||||
return "application/xml; charset=\"utf-8\"";
|
||||
}
|
||||
|
||||
virtual int requestBodyLength() const
|
||||
{
|
||||
return strlen(PROPFIND_REQUEST_BODY);
|
||||
}
|
||||
|
||||
virtual int getBodyData(char* buf, int count) const
|
||||
{
|
||||
int bodyLen = strlen(PROPFIND_REQUEST_BODY);
|
||||
assert(count >= bodyLen);
|
||||
memcpy(buf, PROPFIND_REQUEST_BODY, bodyLen);
|
||||
return bodyLen;
|
||||
requestHeader("Depth") = "0";
|
||||
setBodyData( PROPFIND_REQUEST_BODY,
|
||||
"application/xml; charset=\"utf-8\"" );
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -166,14 +136,19 @@ namespace { // anonmouse
|
||||
SG_LOG(SG_IO, SG_WARN, "request for:" << url() <<
|
||||
" return code " << responseCode());
|
||||
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void responseComplete()
|
||||
virtual void onDone()
|
||||
{
|
||||
if (responseCode() == 207) {
|
||||
_davStatus.finishParse();
|
||||
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
|
||||
if (_davStatus.isValid()) {
|
||||
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
|
||||
} else {
|
||||
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,69 +159,58 @@ namespace { // anonmouse
|
||||
}
|
||||
_davStatus.parseXML(s, n);
|
||||
}
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
HTTP::Request::onFail();
|
||||
if (_repo) {
|
||||
_repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SVNRepoPrivate* _repo;
|
||||
DAVMultiStatus _davStatus;
|
||||
};
|
||||
|
||||
class UpdateReportRequest : public HTTP::Request
|
||||
class UpdateReportRequest:
|
||||
public HTTP::Request
|
||||
{
|
||||
public:
|
||||
UpdateReportRequest(SVNRepoPrivate* repo,
|
||||
const std::string& aVersionName,
|
||||
bool startEmpty) :
|
||||
HTTP::Request("", "REPORT"),
|
||||
_requestSent(0),
|
||||
_parser(repo->p),
|
||||
_repo(repo),
|
||||
_failed(false)
|
||||
{
|
||||
setUrl(repo->vccUrl);
|
||||
|
||||
_request =
|
||||
std::string request =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
|
||||
"<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
|
||||
"<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
|
||||
"<S:depth>unknown</S:depth>\n";
|
||||
"<S:depth>unknown</S:depth>\n"
|
||||
"<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
|
||||
|
||||
_request += "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
|
||||
|
||||
if (!startEmpty) {
|
||||
string_list entries;
|
||||
_repo->rootCollection->mergeUpdateReportDetails(0, entries);
|
||||
BOOST_FOREACH(string e, entries) {
|
||||
_request += e + "\n";
|
||||
}
|
||||
if( !startEmpty )
|
||||
{
|
||||
string_list entries;
|
||||
_repo->rootCollection->mergeUpdateReportDetails(0, entries);
|
||||
BOOST_FOREACH(string e, entries)
|
||||
{
|
||||
request += e + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
_request += "</S:update-report>";
|
||||
}
|
||||
request += "</S:update-report>";
|
||||
|
||||
virtual string requestBodyType() const
|
||||
{
|
||||
return "application/xml; charset=\"utf-8\"";
|
||||
}
|
||||
|
||||
virtual int requestBodyLength() const
|
||||
{
|
||||
return _request.size();
|
||||
}
|
||||
|
||||
virtual int getBodyData(char* buf, int count) const
|
||||
{
|
||||
int len = std::min(count, requestBodyLength() - _requestSent);
|
||||
memcpy(buf, _request.c_str() + _requestSent, len);
|
||||
_requestSent += len;
|
||||
return len;
|
||||
setBodyData(request, "application/xml; charset=\"utf-8\"");
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void responseHeadersComplete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual void responseComplete()
|
||||
virtual void onDone()
|
||||
{
|
||||
if (_failed) {
|
||||
return;
|
||||
@@ -265,8 +229,8 @@ protected:
|
||||
_failed = true;
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
|
||||
" return code " << responseCode());
|
||||
_repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
" got HTTP status " << responseCode());
|
||||
_repo->updateFailed(this, SVNRepository::SVN_ERROR_HTTP);
|
||||
_failed = true;
|
||||
}
|
||||
}
|
||||
@@ -281,20 +245,24 @@ protected:
|
||||
return;
|
||||
}
|
||||
|
||||
//cout << "body data:" << string(s, n) << endl;
|
||||
SVNRepository::ResultCode err = _parser.parseXML(s, n);
|
||||
if (err) {
|
||||
_failed = true;
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
|
||||
" XML parse failed");
|
||||
SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
|
||||
_repo->updateFailed(this, err);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
HTTP::Request::onFail();
|
||||
if (_repo) {
|
||||
_repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
private:
|
||||
string _request;
|
||||
mutable int _requestSent;
|
||||
SVNReportParser _parser;
|
||||
SVNRepoPrivate* _repo;
|
||||
bool _failed;
|
||||
|
||||
@@ -61,7 +61,9 @@ public:
|
||||
SVN_ERROR_XML,
|
||||
SVN_ERROR_TXDELTA,
|
||||
SVN_ERROR_IO,
|
||||
SVN_ERROR_CHECKSUM
|
||||
SVN_ERROR_CHECKSUM,
|
||||
SVN_ERROR_FILE_NOT_FOUND,
|
||||
SVN_ERROR_HTTP
|
||||
};
|
||||
|
||||
ResultCode failure() const;
|
||||
|
||||
@@ -32,10 +32,12 @@ int main(int argc, char* argv[])
|
||||
httpClient = &cl;
|
||||
|
||||
|
||||
SGPath p("/Users/jmt/Desktop/scenemodels");
|
||||
SGPath p("/Users/jmt/Desktop/traffic");
|
||||
SVNRepository airports(p, &cl);
|
||||
// airports.setBaseUrl("http://svn.goneabitbursar.com/testproject1");
|
||||
airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Models");
|
||||
// airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Models");
|
||||
|
||||
airports.setBaseUrl("http://fgfs.goneabitbursar.com/fgfsai/trunk/AI/Traffic");
|
||||
|
||||
// airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Airports");
|
||||
airports.update();
|
||||
|
||||
@@ -48,35 +48,11 @@ public:
|
||||
}
|
||||
|
||||
string key = h.substr(0, colonPos);
|
||||
_headers[key] = h.substr(colonPos + 1);
|
||||
requestHeader(key) = h.substr(colonPos + 1);
|
||||
}
|
||||
|
||||
virtual string_list requestHeaders() const
|
||||
{
|
||||
string_list r;
|
||||
std::map<string, string>::const_iterator it;
|
||||
for (it = _headers.begin(); it != _headers.end(); ++it) {
|
||||
r.push_back(it->first);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
virtual string header(const string& name) const
|
||||
{
|
||||
std::map<string, string>::const_iterator it = _headers.find(name);
|
||||
if (it == _headers.end()) {
|
||||
return string();
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
protected:
|
||||
virtual void responseHeadersComplete()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void responseComplete()
|
||||
virtual void onDone()
|
||||
{
|
||||
_complete = true;
|
||||
}
|
||||
@@ -88,7 +64,6 @@ protected:
|
||||
private:
|
||||
bool _complete;
|
||||
SGFile* _file;
|
||||
std::map<string, string> _headers;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
|
||||
@@ -454,6 +454,23 @@ bool Socket::open ( bool stream )
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SO_NOSIGPIPE
|
||||
// Do not generate SIGPIPE signal (which immediately terminates the program),
|
||||
// instead ::send() will return -1 and errno will be set to EPIPE.
|
||||
// SO_NOSIGPIPE should be available on Mac/BSD systems, but is not available
|
||||
// within Posix/Linux.
|
||||
// This only works for calls to ::send() but not for ::write():
|
||||
// http://freebsd.1045724.n5.nabble.com/is-setsockopt-SO-NOSIGPIPE-work-tp4011054p4011055.html
|
||||
int set = 1;
|
||||
setsockopt(handle, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(set));
|
||||
#endif
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
# define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
// TODO supress SIGPIPE if neither SO_NOSIGPIPE nor MSG_NOSIGNAL is available
|
||||
// http://krokisplace.blogspot.co.at/2010/02/suppressing-sigpipe-in-library.html
|
||||
|
||||
return (handle != -1);
|
||||
}
|
||||
|
||||
@@ -592,7 +609,7 @@ int Socket::connect ( IPAddress* addr )
|
||||
int Socket::send (const void * buffer, int size, int flags)
|
||||
{
|
||||
assert ( handle != -1 ) ;
|
||||
return ::send (handle, (const char*)buffer, size, flags);
|
||||
return ::send (handle, (const char*)buffer, size, flags | MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -600,8 +617,12 @@ int Socket::sendto ( const void * buffer, int size,
|
||||
int flags, const IPAddress* to )
|
||||
{
|
||||
assert ( handle != -1 ) ;
|
||||
return ::sendto(handle,(const char*)buffer,size,flags,
|
||||
(const sockaddr*) to->getAddr(), to->getAddrLen());
|
||||
return ::sendto( handle,
|
||||
(const char*)buffer,
|
||||
size,
|
||||
flags | MSG_NOSIGNAL,
|
||||
(const sockaddr*)to->getAddr(),
|
||||
to->getAddrLen() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/math/SGGeometry.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "lowlevel.hxx"
|
||||
#include "sg_binobj.hxx"
|
||||
@@ -319,15 +320,18 @@ void SGBinObject::read_object( gzFile fp,
|
||||
}
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "We detected an error reading object properties" << endl;
|
||||
return;
|
||||
throw sg_exception("Error reading object properties");
|
||||
}
|
||||
|
||||
size_t indexCount = std::bitset<32>(idx_mask).count();
|
||||
if (indexCount == 0) {
|
||||
throw sg_exception("object index mask has no bits set");
|
||||
}
|
||||
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
if ( sgReadError() ) {
|
||||
cout << "We detected an error reading element size for :" << j << endl;
|
||||
return;
|
||||
throw sg_exception("Error reading element size");
|
||||
}
|
||||
|
||||
buf.resize( nbytes );
|
||||
@@ -335,8 +339,7 @@ void SGBinObject::read_object( gzFile fp,
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "We detected an error reading object element:" << j << "bytes="<< nbytes << endl;
|
||||
return;
|
||||
throw sg_exception("Error reading element bytes");
|
||||
}
|
||||
|
||||
int_list vs;
|
||||
@@ -405,7 +408,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
SG_LOG( SG_EVENT, SG_ALERT,
|
||||
"ERROR: opening " << file << " or " << filegz << " for reading!");
|
||||
|
||||
return false;
|
||||
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,9 +426,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
} else {
|
||||
// close the file before we return
|
||||
gzclose(fp);
|
||||
SG_LOG( SG_EVENT, SG_ALERT,
|
||||
"ERROR: " << file << "has bad header");
|
||||
return false;
|
||||
throw sg_io_exception("Bad BTG magic/version", sg_location(file));
|
||||
}
|
||||
|
||||
// read creation time
|
||||
@@ -462,8 +463,7 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
//cout << "Total objects to read = " << nobjects << endl;
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "Error while reading header of file " << file << "(.gz)" << endl;
|
||||
return false;
|
||||
throw sg_io_exception("Error reading BTG file header", sg_location(file));
|
||||
}
|
||||
|
||||
// read in objects
|
||||
@@ -613,19 +613,13 @@ bool SGBinObject::read_bin( const string& file ) {
|
||||
}
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "Error while reading object:" << i << " in file " << file << "(.gz)" << endl;
|
||||
return false;
|
||||
throw sg_io_exception("Error while reading object", sg_location(file, i));
|
||||
}
|
||||
}
|
||||
|
||||
// close the file
|
||||
gzclose(fp);
|
||||
|
||||
if ( sgReadError() ) {
|
||||
cout << "Error while reading file " << file << "(.gz)" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -665,7 +659,7 @@ void SGBinObject::write_objects(gzFile fp, int type, const group_list& verts,
|
||||
if (verts.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
unsigned int start = 0, end = 1;
|
||||
string m;
|
||||
int_list emptyList;
|
||||
@@ -688,6 +682,13 @@ void SGBinObject::write_objects(gzFile fp, int type, const group_list& verts,
|
||||
if ( !normals.empty() && !normals.front().empty()) idx_mask |= SG_IDX_NORMALS;
|
||||
if ( !colors.empty() && !colors.front().empty()) idx_mask |= SG_IDX_COLORS;
|
||||
if ( !texCoords.empty() && !texCoords.front().empty()) idx_mask |= SG_IDX_TEXCOORDS;
|
||||
|
||||
if (idx_mask == 0) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "SGBinObject::write_objects: object with material:"
|
||||
<< m << "has no indices set");
|
||||
}
|
||||
|
||||
|
||||
sgWriteChar( fp, (char)SG_INDEX_TYPES ); // property
|
||||
sgWriteUInt( fp, 1 ); // nbytes
|
||||
sgWriteChar( fp, idx_mask );
|
||||
@@ -934,7 +935,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
|
||||
fprintf(fp, "\n");
|
||||
|
||||
// dump individual triangles if they exist
|
||||
if ( tris_v.size() > 0 ) {
|
||||
if ( ! tris_v.empty() ) {
|
||||
fprintf(fp, "# triangle groups\n");
|
||||
|
||||
int start = 0;
|
||||
@@ -982,7 +983,7 @@ bool SGBinObject::write_ascii( const string& base, const string& name,
|
||||
}
|
||||
|
||||
// dump triangle groups
|
||||
if ( strips_v.size() > 0 ) {
|
||||
if ( ! strips_v.empty() ) {
|
||||
fprintf(fp, "# triangle strips\n");
|
||||
|
||||
int start = 0;
|
||||
|
||||
@@ -123,6 +123,8 @@ public:
|
||||
void addChannel(NetChannel* channel);
|
||||
void removeChannel(NetChannel* channel);
|
||||
|
||||
bool hasChannels() const { return !channels.empty(); }
|
||||
|
||||
bool poll(unsigned int timeout = 0);
|
||||
void loop(unsigned int timeout = 0);
|
||||
};
|
||||
|
||||
@@ -70,6 +70,8 @@ class NetChat : public NetBufferChannel
|
||||
{
|
||||
std::string terminator;
|
||||
int bytesToCollect;
|
||||
|
||||
protected:
|
||||
virtual void handleBufferRead (NetBuffer& buffer) ;
|
||||
|
||||
public:
|
||||
|
||||
@@ -103,9 +103,14 @@ int SGSocketUDP::read( char *buf, int length ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (length <= 0) {
|
||||
return 0;
|
||||
}
|
||||
int result;
|
||||
// prevent buffer overflow
|
||||
int maxsize = std::min(length - 1, SG_IO_MAX_MSG_SIZE);
|
||||
|
||||
if ( (result = sock.recv(buf, SG_IO_MAX_MSG_SIZE, 0)) >= 0 ) {
|
||||
if ( (result = sock.recv(buf, maxsize, 0)) >= 0 ) {
|
||||
buf[result] = '\0';
|
||||
// printf("msg received = %s\n", buf);
|
||||
}
|
||||
@@ -120,10 +125,16 @@ int SGSocketUDP::readline( char *buf, int length ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (length <= 0) {
|
||||
return 0;
|
||||
}
|
||||
// cout << "sock = " << sock << endl;
|
||||
|
||||
char *buf_ptr = save_buf + save_len;
|
||||
int result = sock.recv(buf_ptr, SG_IO_MAX_MSG_SIZE, 0);
|
||||
// prevent buffer overflow (size of save_buf is 2 * SG_IO_MAX_MSG_SIZE)
|
||||
int maxsize = save_len < SG_IO_MAX_MSG_SIZE ?
|
||||
SG_IO_MAX_MSG_SIZE : 2 * SG_IO_MAX_MSG_SIZE - save_len;
|
||||
int result = sock.recv(buf_ptr, maxsize, 0);
|
||||
// printf("msg received = %s\n", buf);
|
||||
save_len += result;
|
||||
|
||||
@@ -142,6 +153,8 @@ int SGSocketUDP::readline( char *buf, int length ) {
|
||||
// we found an end of line
|
||||
|
||||
// copy to external buffer
|
||||
// prevent buffer overflow
|
||||
result = std::min(result,length - 1);
|
||||
strncpy( buf, save_buf, result );
|
||||
buf[result] = '\0';
|
||||
// cout << "sg_socket line = " << buf << endl;
|
||||
|
||||
@@ -53,48 +53,23 @@ public:
|
||||
bool complete;
|
||||
bool failed;
|
||||
string bodyData;
|
||||
string bodyContentType;
|
||||
|
||||
|
||||
TestRequest(const std::string& url, const std::string method = "GET") :
|
||||
HTTP::Request(url, method),
|
||||
complete(false)
|
||||
{
|
||||
bodyContentType = "text/plain";
|
||||
|
||||
}
|
||||
|
||||
std::map<string, string> sendHeaders;
|
||||
std::map<string, string> headers;
|
||||
protected:
|
||||
string_list requestHeaders() const
|
||||
{
|
||||
string_list r;
|
||||
std::map<string, string>::const_iterator it;
|
||||
for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
|
||||
r.push_back(it->first);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
string header(const string& name) const
|
||||
{
|
||||
std::map<string, string>::const_iterator it = sendHeaders.find(name);
|
||||
if (it == sendHeaders.end()) {
|
||||
return string();
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
virtual void responseHeadersComplete()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void responseComplete()
|
||||
virtual void onDone()
|
||||
{
|
||||
complete = true;
|
||||
}
|
||||
|
||||
virtual void failure()
|
||||
virtual void onFail()
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
@@ -105,11 +80,6 @@ protected:
|
||||
bodyData += string(s, n);
|
||||
}
|
||||
|
||||
virtual std::string requestBodyType() const
|
||||
{
|
||||
return bodyContentType;
|
||||
}
|
||||
|
||||
virtual void responseHeader(const string& header, const string& value)
|
||||
{
|
||||
headers[header] = value;
|
||||
@@ -477,7 +447,9 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
|
||||
HTTP::Client cl;
|
||||
|
||||
// force all requests to use the same connection for this test
|
||||
cl.setMaxConnections(1);
|
||||
|
||||
// test URL parsing
|
||||
TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
|
||||
COMPARE(tr1->scheme(), "http");
|
||||
@@ -533,8 +505,8 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
|
||||
HTTP::Request_ptr own(tr);
|
||||
tr->sendHeaders["X-Foo"] = "Bar";
|
||||
tr->sendHeaders["X-AnotherHeader"] = "A longer value";
|
||||
tr->requestHeader("X-Foo") = "Bar";
|
||||
tr->requestHeader("X-AnotherHeader") = "A longer value";
|
||||
cl.makeRequest(tr);
|
||||
|
||||
waitForComplete(&cl, tr);
|
||||
@@ -657,6 +629,7 @@ int main(int argc, char* argv[])
|
||||
cout << "testing HTTP 1.1 pipelineing" << endl;
|
||||
|
||||
{
|
||||
|
||||
cl.setProxy("", 80);
|
||||
TestRequest* tr = new TestRequest("http://localhost:2000/test1");
|
||||
HTTP::Request_ptr own(tr);
|
||||
@@ -718,7 +691,7 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
cout << "POST" << endl;
|
||||
TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
|
||||
tr->bodyContentType = "application/x-www-form-urlencoded";
|
||||
tr->setBodyData("", "application/x-www-form-urlencoded");
|
||||
|
||||
HTTP::Request_ptr own(tr);
|
||||
cl.makeRequest(tr);
|
||||
|
||||
@@ -3,6 +3,7 @@ include (SimGearComponent)
|
||||
|
||||
set(HEADERS
|
||||
CSSBorder.hxx
|
||||
ListDiff.hxx
|
||||
ResourceManager.hxx
|
||||
interpolator.hxx
|
||||
make_new.hxx
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace simgear
|
||||
{
|
||||
ret.val[i] = offsets.val[i];
|
||||
if( !types.rel[i] )
|
||||
ret.val[i] /= (i & 1) ? dim.height() : dim.width();
|
||||
ret.val[i] /= (i & 1) ? dim.width() : dim.height();
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -75,7 +75,7 @@ namespace simgear
|
||||
{
|
||||
ret.val[i] = offsets.val[i];
|
||||
if( types.rel[i] )
|
||||
ret.val[i] *= (i & 1) ? dim.height() : dim.width();
|
||||
ret.val[i] *= (i & 1) ? dim.width() : dim.height();
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -55,14 +55,21 @@ int main (int ac, char ** av)
|
||||
COMPARE(o.b, 15);
|
||||
COMPARE(o.l, 20);
|
||||
|
||||
b = CSSBorder::parse("5% 10% 15% 20%");
|
||||
o = b.getAbsOffsets(SGRect<int>(0,0,200,200));
|
||||
COMPARE(o.t, 10);
|
||||
COMPARE(o.r, 20);
|
||||
COMPARE(o.b, 30);
|
||||
COMPARE(o.l, 40);
|
||||
b = CSSBorder::parse("5 10 15 20");
|
||||
o = b.getRelOffsets(SGRect<int>(0,0,100,200));
|
||||
COMPARE(o.t, 0.025);
|
||||
COMPARE(o.r, 0.1);
|
||||
COMPARE(o.b, 0.075);
|
||||
COMPARE(o.l, 0.2);
|
||||
|
||||
o = b.getRelOffsets(SGRect<int>(0,0,200,200));
|
||||
b = CSSBorder::parse("5% 10% 15% 20%");
|
||||
o = b.getAbsOffsets(SGRect<int>(0,0,100,200));
|
||||
COMPARE(o.t, 10);
|
||||
COMPARE(o.r, 10);
|
||||
COMPARE(o.b, 30);
|
||||
COMPARE(o.l, 20);
|
||||
|
||||
o = b.getRelOffsets(SGRect<int>(0,0,100,200));
|
||||
COMPARE(o.t, 0.05);
|
||||
COMPARE(o.r, 0.1);
|
||||
COMPARE(o.b, 0.15);
|
||||
|
||||
85
simgear/misc/ListDiff.hxx
Normal file
85
simgear/misc/ListDiff.hxx
Normal file
@@ -0,0 +1,85 @@
|
||||
///@file
|
||||
/// Compare lists and get differences
|
||||
///
|
||||
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_LISTDIFF_HXX_
|
||||
#define SG_LISTDIFF_HXX_
|
||||
|
||||
#include <vector>
|
||||
#include <boost/function.hpp>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
template<class T>
|
||||
struct ListDiff
|
||||
{
|
||||
typedef std::vector<T> List;
|
||||
typedef boost::function<void (T)> Callback;
|
||||
|
||||
/**
|
||||
* Perform list diff in-place (modifies both lists) and call cb_add for
|
||||
* every element in new_list not in old_list and cb_remove for every element
|
||||
* in old_list not in new_list.
|
||||
*/
|
||||
static void inplace( List& old_list,
|
||||
List& new_list,
|
||||
Callback cb_add,
|
||||
Callback cb_remove )
|
||||
{
|
||||
// Check which elements have been removed. (Removing first and adding
|
||||
// second should keep the memory usage lower - not for this function, but
|
||||
// probably for users of this function which use the callbacks to delete
|
||||
// and create objects)
|
||||
while( !old_list.empty() )
|
||||
{
|
||||
T& old_el = old_list.front();
|
||||
typename List::iterator new_el =
|
||||
std::find(new_list.begin(), new_list.end(), old_el);
|
||||
|
||||
if( new_el == new_list.end() )
|
||||
{
|
||||
if( cb_remove )
|
||||
cb_remove(old_el);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Element is in both lists -> just ignore
|
||||
*new_el = new_list.back();
|
||||
new_list.pop_back();
|
||||
}
|
||||
|
||||
old_list.front() = old_list.back();
|
||||
old_list.pop_back();
|
||||
}
|
||||
|
||||
// All remaing elements in new_list have not been in old_list, so call
|
||||
// the add callback for every element if required.
|
||||
if( cb_add )
|
||||
{
|
||||
for( typename List::iterator it = new_list.begin();
|
||||
it != new_list.end();
|
||||
++it )
|
||||
cb_add(*it);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_LISTDIFF_HXX_ */
|
||||
@@ -43,6 +43,30 @@ void test_dir()
|
||||
cout << temp.path().modTime() << endl;
|
||||
}
|
||||
|
||||
SGPath::Permissions validateNone(const SGPath&)
|
||||
{
|
||||
SGPath::Permissions p;
|
||||
p.read = false;
|
||||
p.write = false;
|
||||
return p;
|
||||
}
|
||||
|
||||
SGPath::Permissions validateRead(const SGPath&)
|
||||
{
|
||||
SGPath::Permissions p;
|
||||
p.read = true;
|
||||
p.write = false;
|
||||
return p;
|
||||
}
|
||||
|
||||
SGPath::Permissions validateWrite(const SGPath&)
|
||||
{
|
||||
SGPath::Permissions p;
|
||||
p.read = false;
|
||||
p.write = true;
|
||||
return p;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
SGPath pa;
|
||||
@@ -143,7 +167,26 @@ int main(int argc, char* argv[])
|
||||
COMPARE(pf.dir(), "");
|
||||
COMPARE(pf.lower_extension(), "gz");
|
||||
COMPARE(pf.complete_lower_extension(), "txt.gz");
|
||||
|
||||
|
||||
COMPARE(pf.canRead(), true);
|
||||
COMPARE(pf.canWrite(), true);
|
||||
|
||||
SGPath pp(&validateNone);
|
||||
COMPARE(pp.canRead(), false);
|
||||
COMPARE(pp.canWrite(), false);
|
||||
|
||||
pp.append("./test-dir/file.txt");
|
||||
COMPARE(pp.create_dir(0700), -3);
|
||||
|
||||
pp.setPermissonChecker(&validateRead);
|
||||
COMPARE(pp.canRead(), true);
|
||||
COMPARE(pp.canWrite(), false);
|
||||
COMPARE(pp.create_dir(0700), -3);
|
||||
|
||||
pp.setPermissonChecker(&validateWrite);
|
||||
COMPARE(pp.canRead(), false);
|
||||
COMPARE(pp.canWrite(), true);
|
||||
|
||||
test_dir();
|
||||
|
||||
cout << "all tests passed OK" << endl;
|
||||
|
||||
@@ -149,7 +149,11 @@ PathList Dir::children(int types, const std::string& nameFilter) const
|
||||
WIN32_FIND_DATA fData;
|
||||
HANDLE find = FindFirstFile(search.c_str(), &fData);
|
||||
if (find == INVALID_HANDLE_VALUE) {
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Dir::children: FindFirstFile failed:" << _path.str());
|
||||
int err = GetLastError();
|
||||
if (err != ERROR_FILE_NOT_FOUND) {
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Dir::children: FindFirstFile failed:" <<
|
||||
_path.str() << " with error:" << err);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,27 +75,35 @@ SGPath::fix()
|
||||
|
||||
|
||||
// default constructor
|
||||
SGPath::SGPath()
|
||||
SGPath::SGPath(PermissonChecker validator)
|
||||
: path(""),
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// create a path based on "path"
|
||||
SGPath::SGPath( const std::string& p )
|
||||
SGPath::SGPath( const std::string& p, PermissonChecker validator )
|
||||
: path(p),
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(true)
|
||||
{
|
||||
fix();
|
||||
}
|
||||
|
||||
// create a path based on "path" and a "subpath"
|
||||
SGPath::SGPath( const SGPath& p, const std::string& r )
|
||||
SGPath::SGPath( const SGPath& p,
|
||||
const std::string& r,
|
||||
PermissonChecker validator )
|
||||
: path(p.path),
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(p._cacheEnabled)
|
||||
{
|
||||
append(r);
|
||||
@@ -104,8 +112,12 @@ SGPath::SGPath( const SGPath& p, const std::string& r )
|
||||
|
||||
SGPath::SGPath(const SGPath& p) :
|
||||
path(p.path),
|
||||
_permission_checker(p._permission_checker),
|
||||
_cached(p._cached),
|
||||
_rwCached(p._rwCached),
|
||||
_cacheEnabled(p._cacheEnabled),
|
||||
_canRead(p._canRead),
|
||||
_canWrite(p._canWrite),
|
||||
_exists(p._exists),
|
||||
_isDir(p._isDir),
|
||||
_isFile(p._isFile),
|
||||
@@ -116,8 +128,12 @@ SGPath::SGPath(const SGPath& p) :
|
||||
SGPath& SGPath::operator=(const SGPath& p)
|
||||
{
|
||||
path = p.path;
|
||||
_permission_checker = p._permission_checker,
|
||||
_cached = p._cached;
|
||||
_rwCached = p._rwCached;
|
||||
_cacheEnabled = p._cacheEnabled;
|
||||
_canRead = p._canRead;
|
||||
_canWrite = p._canWrite;
|
||||
_exists = p._exists;
|
||||
_isDir = p._isDir;
|
||||
_isFile = p._isFile;
|
||||
@@ -135,16 +151,31 @@ void SGPath::set( const string& p ) {
|
||||
path = p;
|
||||
fix();
|
||||
_cached = false;
|
||||
_rwCached = false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void SGPath::setPermissonChecker(PermissonChecker validator)
|
||||
{
|
||||
_permission_checker = validator;
|
||||
_rwCached = false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
SGPath::PermissonChecker SGPath::getPermissonChecker() const
|
||||
{
|
||||
return _permission_checker;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void SGPath::set_cached(bool cached)
|
||||
{
|
||||
_cacheEnabled = cached;
|
||||
_cacheEnabled = cached;
|
||||
}
|
||||
|
||||
// append another piece to the existing path
|
||||
void SGPath::append( const string& p ) {
|
||||
if ( path.size() == 0 ) {
|
||||
if ( path.empty() ) {
|
||||
path = p;
|
||||
} else {
|
||||
if ( p[0] != sgDirPathSep ) {
|
||||
@@ -154,6 +185,7 @@ void SGPath::append( const string& p ) {
|
||||
}
|
||||
fix();
|
||||
_cached = false;
|
||||
_rwCached = false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -173,13 +205,14 @@ void SGPath::add( const string& p ) {
|
||||
// concatenate a string to the end of the path without inserting a
|
||||
// path separator
|
||||
void SGPath::concat( const string& p ) {
|
||||
if ( path.size() == 0 ) {
|
||||
if ( path.empty() ) {
|
||||
path = p;
|
||||
} else {
|
||||
path += p;
|
||||
}
|
||||
fix();
|
||||
_cached = false;
|
||||
_rwCached = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -278,7 +311,7 @@ void SGPath::validate() const
|
||||
if (_cached && _cacheEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
struct _stat buf ;
|
||||
|
||||
@@ -310,12 +343,46 @@ void SGPath::validate() const
|
||||
_cached = true;
|
||||
}
|
||||
|
||||
void SGPath::checkAccess() const
|
||||
{
|
||||
if( _rwCached && _cacheEnabled )
|
||||
return;
|
||||
|
||||
if( _permission_checker )
|
||||
{
|
||||
Permissions p = _permission_checker(*this);
|
||||
_canRead = p.read;
|
||||
_canWrite = p.write;
|
||||
}
|
||||
else
|
||||
{
|
||||
_canRead = true;
|
||||
_canWrite = true;
|
||||
}
|
||||
|
||||
_rwCached = true;
|
||||
}
|
||||
|
||||
bool SGPath::exists() const
|
||||
{
|
||||
validate();
|
||||
return _exists;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool SGPath::canRead() const
|
||||
{
|
||||
checkAccess();
|
||||
return _canRead;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool SGPath::canWrite() const
|
||||
{
|
||||
checkAccess();
|
||||
return _canWrite;
|
||||
}
|
||||
|
||||
bool SGPath::isDir() const
|
||||
{
|
||||
validate();
|
||||
@@ -344,7 +411,7 @@ int SGPath::create_dir( mode_t mode ) {
|
||||
bool absolute = !path.empty() && path[0] == sgDirPathSep;
|
||||
|
||||
unsigned int i = 1;
|
||||
SGPath dir = absolute ? string( 1, sgDirPathSep ) : "";
|
||||
SGPath dir(absolute ? string( 1, sgDirPathSep ) : "", _permission_checker);
|
||||
dir.concat( path_elements[0] );
|
||||
#ifdef _WIN32
|
||||
if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) {
|
||||
@@ -360,16 +427,26 @@ int SGPath::create_dir( mode_t mode ) {
|
||||
if ( r == 0 ) {
|
||||
return 0; // Directory already exists
|
||||
}
|
||||
if ( sgMkDir( dir.c_str(), mode) ) {
|
||||
SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
|
||||
for(;;)
|
||||
{
|
||||
if( !dir.canWrite() )
|
||||
{
|
||||
SG_LOG( SG_IO,
|
||||
SG_WARN, "Error creating directory: (" << dir.str() << ")" <<
|
||||
" reason: access denied" );
|
||||
return -3;
|
||||
}
|
||||
else if( sgMkDir(dir.c_str(), mode) )
|
||||
{
|
||||
SG_LOG( SG_IO,
|
||||
SG_ALERT, "Error creating directory: (" << dir.str() << ")" );
|
||||
return -2;
|
||||
}
|
||||
for(; i < path_elements.size(); i++) {
|
||||
dir.append(path_elements[i]);
|
||||
if ( sgMkDir( dir.c_str(), mode) ) {
|
||||
SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
if( i >= path_elements.size() )
|
||||
return 0;
|
||||
|
||||
dir.append(path_elements[i++]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -378,7 +455,7 @@ int SGPath::create_dir( mode_t mode ) {
|
||||
string_list sgPathBranchSplit( const string &dirpath ) {
|
||||
string_list path_elements;
|
||||
string element, path = dirpath;
|
||||
while ( path.size() ) {
|
||||
while ( ! path.empty() ) {
|
||||
size_t p = path.find( sgDirPathSep );
|
||||
if ( p != string::npos ) {
|
||||
element = path.substr( 0, p );
|
||||
@@ -387,7 +464,7 @@ string_list sgPathBranchSplit( const string &dirpath ) {
|
||||
element = path;
|
||||
path = "";
|
||||
}
|
||||
if ( element.size() )
|
||||
if ( ! element.empty() )
|
||||
path_elements.push_back( element );
|
||||
}
|
||||
return path_elements;
|
||||
@@ -456,13 +533,27 @@ std::string SGPath::str_native() const
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool SGPath::remove()
|
||||
{
|
||||
int err = ::unlink(c_str());
|
||||
if (err) {
|
||||
SG_LOG(SG_IO, SG_WARN, "file remove failed: (" << str() << ") " << strerror(errno));
|
||||
}
|
||||
return (err == 0);
|
||||
if( !canWrite() )
|
||||
{
|
||||
SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ")"
|
||||
" reason: access denied" );
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = ::unlink(c_str());
|
||||
if( err )
|
||||
{
|
||||
SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ") "
|
||||
" reason: " << strerror(errno) );
|
||||
// TODO check if failed unlink can really change any of the cached values
|
||||
}
|
||||
|
||||
_cached = false; // stat again if required
|
||||
_rwCached = false;
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
time_t SGPath::modTime() const
|
||||
@@ -481,17 +572,44 @@ bool SGPath::operator!=(const SGPath& other) const
|
||||
return (path != other.path);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool SGPath::rename(const SGPath& newName)
|
||||
{
|
||||
if (::rename(c_str(), newName.c_str()) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "renamed failed: from " << str() << " to " << newName.str()
|
||||
<< " reason: " << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
path = newName.path;
|
||||
_cached = false;
|
||||
return true;
|
||||
if( !canRead() || !canWrite() || !newName.canWrite() )
|
||||
{
|
||||
SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
|
||||
" to " << newName.str() <<
|
||||
" reason: access denied" );
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SG_WINDOWS
|
||||
if (newName.exists()) {
|
||||
SGPath r(newName);
|
||||
if (!r.remove()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( ::rename(c_str(), newName.c_str()) != 0 )
|
||||
{
|
||||
SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
|
||||
" to " << newName.str() <<
|
||||
" reason: " << strerror(errno) );
|
||||
return false;
|
||||
}
|
||||
|
||||
path = newName.path;
|
||||
|
||||
// Do not remove permission checker (could happen for example if just using
|
||||
// a std::string as new name)
|
||||
if( newName._permission_checker )
|
||||
_permission_checker = newName._permission_checker;
|
||||
|
||||
_cached = false;
|
||||
_rwCached = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -499,22 +617,22 @@ SGPath SGPath::fromEnv(const char* name, const SGPath& def)
|
||||
{
|
||||
const char* val = getenv(name);
|
||||
if( val && val[0] )
|
||||
return SGPath(val);
|
||||
return SGPath(val, def._permission_checker);
|
||||
return def;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
//------------------------------------------------------------------------------
|
||||
SGPath SGPath::home()
|
||||
SGPath SGPath::home(const SGPath& def)
|
||||
{
|
||||
// TODO
|
||||
return SGPath();
|
||||
return def;
|
||||
}
|
||||
#else
|
||||
//------------------------------------------------------------------------------
|
||||
SGPath SGPath::home()
|
||||
SGPath SGPath::home(const SGPath& def)
|
||||
{
|
||||
return fromEnv("HOME");
|
||||
return fromEnv("HOME", def);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -523,7 +641,7 @@ SGPath SGPath::home()
|
||||
#include <ShlObj.h> // for CSIDL
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
SGPath SGPath::desktop()
|
||||
SGPath SGPath::desktop(const SGPath& def)
|
||||
{
|
||||
typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
|
||||
static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
|
||||
@@ -535,39 +653,37 @@ SGPath SGPath::desktop()
|
||||
}
|
||||
|
||||
if (!SHGetSpecialFolderPath){
|
||||
return SGPath();
|
||||
return def;
|
||||
}
|
||||
|
||||
char path[MAX_PATH];
|
||||
if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, false)) {
|
||||
return SGPath(path);
|
||||
return SGPath(path, def._permission_checker);
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" );
|
||||
return SGPath();
|
||||
return def;
|
||||
}
|
||||
#elif __APPLE__
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
SGPath SGPath::desktop()
|
||||
SGPath SGPath::desktop(const SGPath& def)
|
||||
{
|
||||
FSRef ref;
|
||||
OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref);
|
||||
if (err) {
|
||||
return SGPath();
|
||||
}
|
||||
if (err)
|
||||
return def;
|
||||
|
||||
unsigned char path[1024];
|
||||
if (FSRefMakePath(&ref, path, 1024) != noErr) {
|
||||
return SGPath();
|
||||
}
|
||||
if (FSRefMakePath(&ref, path, 1024) != noErr)
|
||||
return def;
|
||||
|
||||
return SGPath((const char*) path);
|
||||
return SGPath((const char*) path, def._permission_checker);
|
||||
}
|
||||
#else
|
||||
//------------------------------------------------------------------------------
|
||||
SGPath SGPath::desktop()
|
||||
SGPath SGPath::desktop(const SGPath& def)
|
||||
{
|
||||
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
|
||||
@@ -594,12 +710,12 @@ SGPath SGPath::desktop()
|
||||
|
||||
const std::string HOME = "$HOME";
|
||||
if( starts_with(line, HOME) )
|
||||
return home() / simgear::strutils::unescape(line.substr(HOME.length()));
|
||||
return home(def) / simgear::strutils::unescape(line.substr(HOME.length()));
|
||||
|
||||
return SGPath(line);
|
||||
return SGPath(line, def._permission_checker);
|
||||
}
|
||||
|
||||
return home() / "Desktop";
|
||||
return home(def) / "Desktop";
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -52,8 +52,15 @@ class SGPath {
|
||||
|
||||
public:
|
||||
|
||||
struct Permissions
|
||||
{
|
||||
bool read : 1;
|
||||
bool write : 1;
|
||||
};
|
||||
typedef Permissions (*PermissonChecker)(const SGPath&);
|
||||
|
||||
/** Default constructor */
|
||||
SGPath();
|
||||
explicit SGPath(PermissonChecker validator = NULL);
|
||||
|
||||
/** Copy contructor */
|
||||
SGPath(const SGPath& p);
|
||||
@@ -64,14 +71,16 @@ public:
|
||||
* Construct a path based on the starting path provided.
|
||||
* @param p initial path
|
||||
*/
|
||||
SGPath( const std::string& p );
|
||||
SGPath( const std::string& p, PermissonChecker validator = NULL );
|
||||
|
||||
/**
|
||||
* Construct a path based on the starting path provided and a relative subpath
|
||||
* @param p initial path
|
||||
* @param r relative subpath
|
||||
*/
|
||||
SGPath( const SGPath& p, const std::string& r );
|
||||
SGPath( const SGPath& p,
|
||||
const std::string& r,
|
||||
PermissonChecker validator = NULL );
|
||||
|
||||
/** Destructor */
|
||||
~SGPath();
|
||||
@@ -85,7 +94,10 @@ public:
|
||||
|
||||
bool operator==(const SGPath& other) const;
|
||||
bool operator!=(const SGPath& other) const;
|
||||
|
||||
|
||||
void setPermissonChecker(PermissonChecker validator);
|
||||
PermissonChecker getPermissonChecker() const;
|
||||
|
||||
/**
|
||||
* Set if file information (exists, type, mod-time) is cached or
|
||||
* retrieved each time it is queried. Caching is enabled by default
|
||||
@@ -198,6 +210,17 @@ public:
|
||||
*/
|
||||
int create_dir(mode_t mode);
|
||||
|
||||
/**
|
||||
* Check if reading file is allowed. Readabilty does not imply the existance
|
||||
* of the file.
|
||||
*
|
||||
* @note By default all files will be marked as readable. No check is made
|
||||
* if the operating system allows the given file to be read. Derived
|
||||
* classes may actually implement custom read/write rights.
|
||||
*/
|
||||
bool canRead() const;
|
||||
bool canWrite() const;
|
||||
|
||||
bool isFile() const;
|
||||
bool isDir() const;
|
||||
|
||||
@@ -246,23 +269,28 @@ public:
|
||||
/**
|
||||
* Get path to user's home directory
|
||||
*/
|
||||
static SGPath home();
|
||||
static SGPath home(const SGPath& def = SGPath());
|
||||
|
||||
/**
|
||||
* Get path to the user's desktop directory
|
||||
*/
|
||||
static SGPath desktop();
|
||||
static SGPath desktop(const SGPath& def = SGPath());
|
||||
|
||||
private:
|
||||
|
||||
void fix();
|
||||
|
||||
void validate() const;
|
||||
void checkAccess() const;
|
||||
|
||||
std::string path;
|
||||
|
||||
PermissonChecker _permission_checker;
|
||||
|
||||
mutable bool _cached : 1;
|
||||
mutable bool _rwCached : 1;
|
||||
bool _cacheEnabled : 1; ///< cacheing can be disbled if required
|
||||
mutable bool _canRead : 1;
|
||||
mutable bool _canWrite : 1;
|
||||
mutable bool _exists : 1;
|
||||
mutable bool _isDir : 1;
|
||||
mutable bool _isFile : 1;
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "strutils.hxx"
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::stringstream;
|
||||
@@ -33,6 +35,39 @@ using std::stringstream;
|
||||
namespace simgear {
|
||||
namespace strutils {
|
||||
|
||||
/*
|
||||
* utf8ToLatin1() convert utf8 to latin, useful for accent character (i.e éâàîè...)
|
||||
*/
|
||||
template <typename Iterator> size_t get_length (Iterator p) {
|
||||
unsigned char c = static_cast<unsigned char> (*p);
|
||||
if (c < 0x80) return 1;
|
||||
else if (!(c & 0x20)) return 2;
|
||||
else if (!(c & 0x10)) return 3;
|
||||
else if (!(c & 0x08)) return 4;
|
||||
else if (!(c & 0x04)) return 5;
|
||||
else return 6;
|
||||
}
|
||||
|
||||
typedef unsigned int value_type;
|
||||
template <typename Iterator> value_type get_value (Iterator p) {
|
||||
size_t len = get_length (p);
|
||||
if (len == 1) return *p;
|
||||
value_type res = static_cast<unsigned char> ( *p & (0xff >> (len + 1))) << ((len - 1) * 6 );
|
||||
for (--len; len; --len)
|
||||
res |= (static_cast<unsigned char> (*(++p)) - 0x80) << ((len - 1) * 6);
|
||||
return res;
|
||||
}
|
||||
|
||||
string utf8ToLatin1( string& s_utf8 ) {
|
||||
string s_latin1;
|
||||
for (string::iterator p = s_utf8.begin(); p != s_utf8.end(); ++p) {
|
||||
value_type value = get_value<string::iterator&>(p);
|
||||
if (value > 0xff) SG_LOG(SG_IO, SG_WARN, "utf8ToLatin1: wrong char value: " << value);
|
||||
s_latin1 += static_cast<char>(value);
|
||||
}
|
||||
return s_latin1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -303,26 +338,51 @@ namespace simgear {
|
||||
return rslt;
|
||||
}
|
||||
|
||||
string lowercase(const string &s) {
|
||||
string rslt(s);
|
||||
for(string::iterator p = rslt.begin(); p != rslt.end(); p++){
|
||||
*p = tolower(*p);
|
||||
}
|
||||
return rslt;
|
||||
}
|
||||
|
||||
void lowercase(string &s) {
|
||||
for(string::iterator p = s.begin(); p != s.end(); p++){
|
||||
*p = tolower(*p);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(SG_WINDOWS)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
static WCharVec convertMultiByteToWString(DWORD encoding, const std::string& a)
|
||||
{
|
||||
WCharVec result;
|
||||
DWORD flags = 0;
|
||||
int requiredWideChars = MultiByteToWideChar(encoding, flags,
|
||||
a.c_str(), a.size(),
|
||||
NULL, 0);
|
||||
result.resize(requiredWideChars);
|
||||
MultiByteToWideChar(encoding, flags, a.c_str(), a.size(),
|
||||
result.data(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
WCharVec convertUtf8ToWString(const std::string& a)
|
||||
{
|
||||
return convertMultiByteToWString(CP_UTF8, a);
|
||||
}
|
||||
|
||||
#ifdef SG_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
std::string convertWindowsLocal8BitToUtf8(const std::string& a)
|
||||
{
|
||||
#ifdef SG_WINDOWS
|
||||
DWORD flags = 0;
|
||||
std::vector<wchar_t> wideString;
|
||||
|
||||
// call to query transform size
|
||||
int requiredWideChars = MultiByteToWideChar(CP_ACP, flags, a.c_str(), a.size(),
|
||||
NULL, 0);
|
||||
// allocate storage and call for real
|
||||
wideString.resize(requiredWideChars);
|
||||
MultiByteToWideChar(CP_ACP, flags, a.c_str(), a.size(),
|
||||
wideString.data(), wideString.size());
|
||||
|
||||
// now convert back down to UTF-8
|
||||
WCharVec wideString = convertMultiByteToWString(CP_ACP, a);
|
||||
|
||||
// convert down to UTF-8
|
||||
std::vector<char> result;
|
||||
int requiredUTF8Chars = WideCharToMultiByte(CP_UTF8, flags,
|
||||
wideString.data(), wideString.size(),
|
||||
@@ -337,12 +397,31 @@ std::string convertWindowsLocal8BitToUtf8(const std::string& a)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
|
||||
static const unsigned char base64_decode_map[128] =
|
||||
{
|
||||
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
|
||||
127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
|
||||
127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
|
||||
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
|
||||
29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||
49, 50, 51, 127, 127, 127, 127, 127
|
||||
};
|
||||
|
||||
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
@@ -351,14 +430,13 @@ static bool is_whitespace(unsigned char c) {
|
||||
return ((c == ' ') || (c == '\r') || (c == '\n'));
|
||||
}
|
||||
|
||||
std::string decodeBase64(const std::string& encoded_string)
|
||||
void decodeBase64(const std::string& encoded_string, std::vector<unsigned char>& ret)
|
||||
{
|
||||
int in_len = encoded_string.size();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
while (in_len-- && ( encoded_string[in_] != '=')) {
|
||||
if (is_whitespace( encoded_string[in_])) {
|
||||
@@ -373,14 +451,14 @@ std::string decodeBase64(const std::string& encoded_string)
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if (i ==4) {
|
||||
for (i = 0; i <4; i++)
|
||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||
char_array_4[i] = base64_decode_map[char_array_4[i]];
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
ret += char_array_3[i];
|
||||
ret.push_back(char_array_3[i]);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
@@ -390,16 +468,14 @@ std::string decodeBase64(const std::string& encoded_string)
|
||||
char_array_4[j] = 0;
|
||||
|
||||
for (j = 0; j <4; j++)
|
||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||
char_array_4[j] = base64_decode_map[char_array_4[j]];
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
for (j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
@@ -484,6 +560,17 @@ std::string unescape(const char* s)
|
||||
return r;
|
||||
}
|
||||
|
||||
string sanitizePrintfFormat(const string& input)
|
||||
{
|
||||
string::size_type i = input.find("%n");
|
||||
if (i != string::npos) {
|
||||
SG_LOG(SG_IO, SG_WARN, "sanitizePrintfFormat: bad format string:" << input);
|
||||
return string();
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
} // end namespace strutils
|
||||
|
||||
} // end namespace simgear
|
||||
|
||||
@@ -38,6 +38,11 @@ typedef std::vector < std::string > string_list;
|
||||
namespace simgear {
|
||||
namespace strutils {
|
||||
|
||||
/**
|
||||
* utf8ToLatin1() convert utf8 to latin, useful for accent character (i.e éâàîè...)
|
||||
*/
|
||||
std::string utf8ToLatin1( std::string & s_utf8 );
|
||||
|
||||
// /**
|
||||
// * atof() wrapper for "string" type
|
||||
// */
|
||||
@@ -137,7 +142,7 @@ namespace simgear {
|
||||
|
||||
/**
|
||||
* Like strcmp(), but for dotted versions strings NN.NN.NN
|
||||
* any number of terms are support.
|
||||
* any number of terms are supported.
|
||||
* @return 0 if versions match, -ve number if v1 is lower, +ve if v1
|
||||
* is greater
|
||||
*/
|
||||
@@ -149,18 +154,34 @@ namespace simgear {
|
||||
*/
|
||||
std::string uppercase(const std::string &s);
|
||||
|
||||
/**
|
||||
* Convert a string to lower case.
|
||||
* @return lower case string
|
||||
*/
|
||||
std::string lowercase(const std::string &s);
|
||||
|
||||
/**
|
||||
* Convert a string to lower case in place
|
||||
*/
|
||||
void lowercase(std::string &s);
|
||||
|
||||
/**
|
||||
* convert a string in the local Windows 8-bit encoding to UTF-8
|
||||
* (no-op on other platforms)
|
||||
*/
|
||||
std::string convertWindowsLocal8BitToUtf8(const std::string& a);
|
||||
|
||||
#if defined(SG_WINDOWS)
|
||||
typedef std::vector<wchar_t> WCharVec;
|
||||
WCharVec convertUtf8ToWString(const std::string& a);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* convert base-64 encoded data to raw bytes (possibly with embedded
|
||||
* NULs). Throws an exception if input data is not base64, or is
|
||||
* malformed
|
||||
*/
|
||||
std::string decodeBase64(const std::string& a);
|
||||
void decodeBase64(const std::string& a, std::vector<unsigned char>& output);
|
||||
|
||||
/**
|
||||
* convert bytes to hexadecimal equivalent
|
||||
@@ -180,6 +201,13 @@ namespace simgear {
|
||||
|
||||
inline std::string unescape(const std::string& str)
|
||||
{ return unescape(str.c_str()); }
|
||||
|
||||
/**
|
||||
* Check a printf-style format string for dangerous (buffer-overflowing,
|
||||
* memory re-writing) format tokens. If a problematic token is
|
||||
* found, logs an error (SG_WARN) and returns an empty format string.
|
||||
*/
|
||||
std::string sanitizePrintfFormat(const std::string& input);
|
||||
|
||||
} // end namespace strutils
|
||||
} // end namespace simgear
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user