Compare commits
91 Commits
version/20
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a73e6c0d5 | ||
|
|
16b5dd5e78 | ||
|
|
1aba20d642 | ||
|
|
27786d709d | ||
|
|
e0e6a29150 | ||
|
|
dfd6076e19 | ||
|
|
e589ef8627 | ||
|
|
e8c1baa396 | ||
|
|
69ba2617d2 | ||
|
|
34c215ad83 | ||
|
|
2a3bb62001 | ||
|
|
fe46ae09ef | ||
|
|
46f67fce7a | ||
|
|
629e68428f | ||
|
|
100439aadd | ||
|
|
f6a348ba94 | ||
|
|
a54b3ffcee | ||
|
|
47842c9ea7 | ||
|
|
834e25521b | ||
|
|
f78597f010 | ||
|
|
c06e433e74 | ||
|
|
9971d517fd | ||
|
|
f8548029a2 | ||
|
|
3e337e3e97 | ||
|
|
e59f8eda74 | ||
|
|
31e70b205c | ||
|
|
5d02f1db5f | ||
|
|
b01bd93a20 | ||
|
|
05bbba5074 | ||
|
|
679d4e1dcd | ||
|
|
aaa6231f89 | ||
|
|
446c8cd70c | ||
|
|
11ff1d256c | ||
|
|
4acd047982 | ||
|
|
ee4b6621e7 | ||
|
|
77517b15fd | ||
|
|
19a86a6f0d | ||
|
|
a7dee1164d | ||
|
|
7512bc0fb2 | ||
|
|
d6b57d0937 | ||
|
|
1e99c4b2ec | ||
|
|
04ca3ad8f4 | ||
|
|
2b8915e35f | ||
|
|
2a0801da6b | ||
|
|
2935d78490 | ||
|
|
2404924bcb | ||
|
|
e4c4db5cf9 | ||
|
|
e1c655c570 | ||
|
|
2788da9c51 | ||
|
|
4860a70443 | ||
|
|
22d1433ab0 | ||
|
|
ef9eedf35a | ||
|
|
a962c90b30 | ||
|
|
36275f5cce | ||
|
|
60d1c87cef | ||
|
|
9223f30f08 | ||
|
|
fe87e7f60d | ||
|
|
f3e83cf020 | ||
|
|
9eee41d74a | ||
|
|
991d76b69e | ||
|
|
6f0c7da6ad | ||
|
|
c170f576b6 | ||
|
|
1446f559cc | ||
|
|
4467e68db1 | ||
|
|
cc1118b330 | ||
|
|
a1126bd42c | ||
|
|
370523d5bf | ||
|
|
96b4d2c03d | ||
|
|
c4898502bf | ||
|
|
f7a511d1b3 | ||
|
|
b66c51a6f8 | ||
|
|
a4cf38925b | ||
|
|
41059a24a7 | ||
|
|
ee4f5a5190 | ||
|
|
141e98564c | ||
|
|
72341a6de4 | ||
|
|
55ee59ac99 | ||
|
|
61525c555e | ||
|
|
dad30b0cc2 | ||
|
|
9c9e4e86e7 | ||
|
|
b88aa46e1c | ||
|
|
10fa8a471a | ||
|
|
4e875be0dd | ||
|
|
23cc940743 | ||
|
|
4b010cc416 | ||
|
|
46f39d5fbd | ||
|
|
03515151f0 | ||
|
|
8cd723d91b | ||
|
|
6f2943ed9a | ||
|
|
e509fc3f5d | ||
|
|
707d9e12cf |
@@ -120,12 +120,14 @@ endif()
|
||||
|
||||
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
|
||||
option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF)
|
||||
option(ENABLE_GDAL "Set to ON to build SimGear with GDAL 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)
|
||||
option(USE_AEONWAVE "Set to ON to use AeonWave instead of OpenAL" OFF)
|
||||
option(ENABLE_PKGUTIL "Set to ON to build the sg_pkgutil application (default)" ON)
|
||||
option(ENABLE_DNS "Set to ON to use udns library and DNS service resolver" ON)
|
||||
option(ENABLE_SIMD "Enable SSE/SSE2 support for x86 compilers" ON)
|
||||
option(ENABLE_OPENMP "Enable OpenMP compiler support" OFF)
|
||||
|
||||
include (DetectArch)
|
||||
|
||||
@@ -247,8 +249,8 @@ else()
|
||||
# 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)
|
||||
${PROJECT_SOURCE_DIR}/3rdparty/expat
|
||||
${PROJECT_BINARY_DIR}/3rdparty/expat)
|
||||
endif(SYSTEM_EXPAT)
|
||||
|
||||
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
||||
@@ -262,13 +264,28 @@ if(HAVE_INTTYPES_H)
|
||||
endif()
|
||||
|
||||
if(ENABLE_RTI)
|
||||
# See if we have any rti library variant installed
|
||||
message(STATUS "RTI: ENABLED")
|
||||
find_package(RTI)
|
||||
find_package(PkgConfig)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
SET(ENV{PKG_CONFIG_PATH} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
||||
pkg_check_modules(RTI hla-rti13)
|
||||
endif(PKG_CONFIG_FOUND)
|
||||
if(RTI_FOUND)
|
||||
SET(RTI_INCLUDE_DIR "${RTI_INCLUDE_DIRS}")
|
||||
message(STATUS "RTI: ENABLED")
|
||||
else()
|
||||
message(STATUS "RTI: DISABLED")
|
||||
endif(RTI_FOUND)
|
||||
else()
|
||||
message(STATUS "RTI: DISABLED")
|
||||
endif(ENABLE_RTI)
|
||||
|
||||
if(ENABLE_GDAL)
|
||||
find_package(GDAL 2.0.0 REQUIRED)
|
||||
if (GDAL_FOUND)
|
||||
include_directories(${GDAL_INCLUDE_DIR})
|
||||
endif(GDAL_FOUND)
|
||||
endif(ENABLE_GDAL)
|
||||
|
||||
check_function_exists(gettimeofday HAVE_GETTIMEOFDAY)
|
||||
check_function_exists(rint HAVE_RINT)
|
||||
check_function_exists(mkdtemp HAVE_MKDTEMP)
|
||||
@@ -335,8 +352,8 @@ if (NOT ${HAVE_STD_ISNAN})
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(WARNING_FLAGS_CXX "-Wall")
|
||||
set(WARNING_FLAGS_C "-Wall")
|
||||
set(WARNING_FLAGS_CXX "-Wall -fPIC")
|
||||
set(WARNING_FLAGS_C "-Wall -fPIC")
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.1)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
@@ -363,8 +380,8 @@ endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
if (CLANG)
|
||||
# Boost redeclares class members
|
||||
set(WARNING_FLAGS_CXX "-Wall -Wno-overloaded-virtual -Wno-redeclared-class-member")
|
||||
set(WARNING_FLAGS_C "-Wall")
|
||||
set(WARNING_FLAGS_CXX "-Wall -fPIC -Wno-overloaded-virtual -Wno-redeclared-class-member")
|
||||
set(WARNING_FLAGS_C "-Wall -fPIC")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
|
||||
# fix Boost compilation :(
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
@@ -381,6 +398,19 @@ if (CLANG)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (ENABLE_OPENMP)
|
||||
find_package(OpenMP)
|
||||
if(OPENMP_FOUND)
|
||||
message(STATUS "OpenMP: ENABLED")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
|
||||
else()
|
||||
message(STATUS "OpenMP: NOT FOUND")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "OpenMP: DISABLED")
|
||||
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)
|
||||
@@ -464,7 +494,8 @@ set(TEST_LIBS_INTERNAL_CORE
|
||||
${RT_LIBRARY}
|
||||
${DL_LIBRARY}
|
||||
${COCOA_LIBRARY}
|
||||
${CURL_LIBRARIES})
|
||||
${CURL_LIBRARIES}
|
||||
${GDAL_LIBRARY})
|
||||
set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
|
||||
|
||||
if(NOT SIMGEAR_HEADLESS)
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
%define ver @VERSION@
|
||||
%define rel 1
|
||||
%define prefix /usr
|
||||
|
||||
Summary: Simulator Construction Gear.
|
||||
Name: @PACKAGE@
|
||||
Version: %ver
|
||||
Release: %rel
|
||||
Copyright: LGPL
|
||||
Group: Libraries/Graphics
|
||||
Source: %{name}-%{version}.tar.gz
|
||||
#URL:
|
||||
BuildRoot: /tmp/%{name}-%{version}-%{rel}-root
|
||||
Packager: Fill In As You Wish
|
||||
Docdir: %{prefix}/doc
|
||||
|
||||
%description
|
||||
This package contains a tools and libraries useful for constructing
|
||||
simulation and visualization applications such as FlightGear or TerraGear.
|
||||
|
||||
Authors:
|
||||
N/A
|
||||
|
||||
%prep
|
||||
%setup -n %{name}-%{version}
|
||||
|
||||
|
||||
%build
|
||||
# Needed for snapshot releases.
|
||||
if [ ! -f configure ]; then
|
||||
CFLAGS="$RPM_OPT_FLAGS" ./autogen.sh --prefix=%prefix
|
||||
else
|
||||
CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%prefix
|
||||
fi
|
||||
|
||||
if [ "$SMP" != "" ]; then
|
||||
JSMP = '"MAKE=make -k -j $SMP"'
|
||||
fi
|
||||
|
||||
make ${JSMP};
|
||||
|
||||
|
||||
%install
|
||||
[ -d ${RPM_BUILD_ROOT} ] && rm -rf ${RPM_BUILD_ROOT}
|
||||
|
||||
make prefix=${RPM_BUILD_ROOT}%{prefix} install
|
||||
|
||||
#
|
||||
# Generating file lists and store them in file-lists
|
||||
# Starting with the directory listings
|
||||
#
|
||||
find ${RPM_BUILD_ROOT}%{prefix}/{bin,include,lib} -type d | sed "s#^${RPM_BUILD_ROOT}#\%attr (-\,root\,root) \%dir #" > file-lists
|
||||
%{?ETCDR:find ${RPM_BUILD_ROOT}%{!?SYSCF:%{prefix}}/etc -type d | sed "s#^${RPM_BUILD_ROOT}#\%attr (-\,root\,root) \%dir #" >> file-lists}
|
||||
|
||||
#
|
||||
# Then, the file listings
|
||||
#
|
||||
echo "%defattr (-, root, root)" >> file-lists
|
||||
%{?ETCDR:find ${RPM_BUILD_ROOT}%{!?SYSCF:%{prefix}}/etc/%{name}.conf -type f | sed -e "s#^${RPM_BUILD_ROOT}#%config #g" >> file-lists}
|
||||
find ${RPM_BUILD_ROOT}%{prefix} -type f | sed -e "s#^${RPM_BUILD_ROOT}##g" >> file-lists
|
||||
|
||||
|
||||
%clean
|
||||
(cd ..; rm -rf %{name}-%{version} ${RPM_BUILD_ROOT})
|
||||
|
||||
|
||||
%files -f file-lists
|
||||
%defattr (-, root, root)
|
||||
%doc AUTHORS
|
||||
%doc COPYING
|
||||
%doc ChangeLog
|
||||
%doc INSTALL
|
||||
%doc NEWS
|
||||
%doc README
|
||||
%doc %{name}.spec.in
|
||||
|
||||
@@ -17,4 +17,9 @@ set(SIMGEAR_SOUND @ENABLE_SOUND@)
|
||||
|
||||
set(ENABLE_SIMD @ENABLE_SIMD@)
|
||||
|
||||
# Alternative terrain engine based on pagedLOD
|
||||
|
||||
set(ENABLE_GDAL @ENABLE_GDAL@)
|
||||
set(ENABLE_OPENMP @ENABLE_OPENMP@)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/SimGearTargets.cmake")
|
||||
|
||||
@@ -5,6 +5,7 @@ foreach( mylibfolder
|
||||
bucket
|
||||
bvh
|
||||
debug
|
||||
embedded_resources
|
||||
ephemeris
|
||||
io
|
||||
magvar
|
||||
@@ -172,6 +173,11 @@ if(NOT SIMGEAR_HEADLESS)
|
||||
${OPENGL_LIBRARY}
|
||||
${JPEG_LIBRARY})
|
||||
|
||||
if(ENABLE_GDAL)
|
||||
target_link_libraries(SimGearScene
|
||||
${GDAL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# only actually needed by canvas/KeyboardEvent.cxx
|
||||
target_include_directories(SimGearScene PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/utf8/source)
|
||||
endif()
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "BVHMaterial.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "BVHPageNode.hxx"
|
||||
|
||||
#include "BVHPager.hxx"
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "BVHPageRequest.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "BVHPager.hxx"
|
||||
|
||||
#include <list>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "BVHStaticNode.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <iostream>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasEventManager.hxx"
|
||||
#include "CanvasEventVisitor.hxx"
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "CanvasEvent.hxx"
|
||||
|
||||
namespace simgear
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasEventManager.hxx"
|
||||
#include <simgear/canvas/events/MouseEvent.hxx>
|
||||
#include <simgear/canvas/elements/CanvasElement.hxx>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasEvent.hxx"
|
||||
#include "CanvasEventVisitor.hxx"
|
||||
#include <simgear/canvas/elements/CanvasElement.hxx>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasMgr.hxx"
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasEventManager.hxx"
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "Canvas.hxx"
|
||||
#include "CanvasObjectPlacement.hxx"
|
||||
#include <simgear/canvas/events/MouseEvent.hxx>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasPlacement.hxx"
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasMgr.hxx"
|
||||
#include "CanvasSystemAdapter.hxx"
|
||||
#include "CanvasWindow.hxx"
|
||||
|
||||
@@ -35,7 +35,7 @@ void SHVector2_dtor(SHVector2 *v) {
|
||||
}
|
||||
|
||||
void SHVector3_ctor(SHVector3 *v) {
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
v->vec = _mm_setzero_ps();
|
||||
#else
|
||||
v->x=0.0f; v->y=0.0f; v->z=0.0f;
|
||||
@@ -46,7 +46,7 @@ void SHVector3_dtor(SHVector3 *v) {
|
||||
}
|
||||
|
||||
void SHVector4_ctor(SHVector4 *v) {
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
v->vec = _mm_setzero_ps();
|
||||
#else
|
||||
v->x=0.0f; v->y=0.0f; v->z=0.0f; v->w=0.0f;
|
||||
@@ -57,7 +57,7 @@ void SHVector4_dtor(SHVector4 *v) {
|
||||
}
|
||||
|
||||
void SHRectangle_ctor(SHRectangle *r) {
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
r->vec = _mm_setzero_ps();
|
||||
#else
|
||||
r->x=0.0f; r->y=0.0f; r->w=0.0f; r->h=0.0f;
|
||||
@@ -148,7 +148,7 @@ int shLineLineXsection(SHVector2 *o1, SHVector2 *v1,
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# ifdef __SSE3__
|
||||
# include <pmmintrin.h>
|
||||
inline float hsum_ps_sse(__m128 v) {
|
||||
|
||||
@@ -21,7 +21,17 @@
|
||||
#ifndef __SHVECTORS_H
|
||||
#define __SHVECTORS_H
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear/simgear_config.h>
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SIMD
|
||||
# ifdef __SSE__
|
||||
// # define SHIVA_USE_SIMD
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# include <xmmintrin.h>
|
||||
float hsum_ps_sse(__m128 v);
|
||||
#endif
|
||||
@@ -41,7 +51,7 @@ void SHVector2_dtor(SHVector2 *v);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
union ALIGN16 {
|
||||
__m128 vec;
|
||||
struct { SHfloat x,y,z,w; };
|
||||
@@ -56,7 +66,7 @@ void SHVector3_dtor(SHVector3 *v);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
union ALIGN16 {
|
||||
__m128 vec;
|
||||
struct { SHfloat x,y,z,w; };
|
||||
@@ -71,7 +81,7 @@ void SHVector4_dtor(SHVector4 *v);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
union ALIGN16 {
|
||||
__m128 vec;
|
||||
struct { SHfloat x,y,w,h; };
|
||||
@@ -88,7 +98,7 @@ void shRectangleSet(SHRectangle *r, SHfloat x,
|
||||
|
||||
typedef struct
|
||||
{
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
union ALIGN16 {
|
||||
__m128 mtx[4];
|
||||
SHfloat m[4][4];
|
||||
@@ -117,7 +127,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
*--------------------------------------------------------- */
|
||||
|
||||
#define SET2(v,xs,ys) { v.x=xs; v.y=ys; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define SET3(v,xs,ys,zs,ws) { v.vec=_mm_set_ps(0,zs,ys,xs); }
|
||||
# define SET4(v,xs,ys,zs,ws) { v.vec=_mm_set_ps(ws,zs,ys,xs); }
|
||||
#else
|
||||
@@ -126,7 +136,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define SET2V(v1,v2) { v1.x=v2.x; v1.y=v2.y; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define SET3V(v1,v2) { v1.vec=v2.vec; }
|
||||
# define SET4V(v1,v2) { v1.vec=v2.vec; }
|
||||
#else
|
||||
@@ -147,7 +157,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#define EQ4V(v1,v2) ( v1.x==v2.x && v1.y==v2.y && v1.z==v2.z && v1.w==v2.w )
|
||||
|
||||
#define ADD2(v,xx,yy) { v.x+=xx; v.y+=yy; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define ADD3(v,xx,yy,zz,ww) { v.vec=_mm_add_ps(v.vec,_mm_set_ps(0,zz,yy,xx)); }
|
||||
# define ADD4(v,xx,yy,zz,ww) { v.vec=_mm_add_ps(v.vec,_mm_set_ps(ww,zz,yy,xx)); }
|
||||
#else
|
||||
@@ -156,7 +166,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define ADD2V(v1,v2) { v1.x+=v2.x; v1.y+=v2.y; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define ADD3V(v1,v2) { v1.vec=_mm_add_ps(v1.vec,v2.vec); }
|
||||
# define ADD4V(v1,v2) { v1.vec=_mm_add_ps(v1.vec,v2.vec); }
|
||||
#else
|
||||
@@ -165,7 +175,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define SUB2(v,xx,yy) { v.x-=xx; v.y-=yy; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define SUB3(v,xx,yy,zz,ww) { v.vec=_mm_sub_ps(v.vec,_mm_set_ps(0,zz,yy,xx)); }
|
||||
# define SUB4(v,xx,yy,zz,ww) { v.vec=_mm_sub_ps(v.vec,_mm_set_ps(ww,zz,yy,xx)); }
|
||||
#else
|
||||
@@ -174,7 +184,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define SUB2V(v1,v2) { v1.x-=v2.x; v1.y-=v2.y; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define SUB3V(v1,v2) { v1.vec=_mm_sub_ps(v1.vec,v2.vec); }
|
||||
# define SUB4V(v1,v2) { v1.vec=_mm_sub_ps(v1.vec,v2.vec); }
|
||||
#else
|
||||
@@ -183,7 +193,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define MUL2(v,f) { v.x*=f; v.y*=f; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define MUL3(v,f) { v.vec=_mm_mul_ps(v.vec,_mm_set1_ps(f)); }
|
||||
# define MUL4(v,f) { v.vec=_mm_mul_ps(v.vec,_mm_set1_ps(f)); }
|
||||
#else
|
||||
@@ -192,7 +202,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define DIV2(v,f) { v.x/=f; v.y/=f; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define DIV3(v,f) { v.vec=_mm_div_ps(v.vec,_mm_set1_ps(f)); }
|
||||
# define DIV4(v,f) { v.vec=_mm_div_ps(v.vec,_mm_set1_ps(f)); }
|
||||
#else
|
||||
@@ -201,7 +211,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#endif
|
||||
|
||||
#define ABS2(v) { v.x=SH_ABS(v.x); v.y=SH_ABS(v.y); }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define ABS_MASK _mm_set1_ps(-0.f)
|
||||
# define ABS3(v) { v.vec=_mm_andnot_ps(ABS_MASK, v.vec); }
|
||||
# define ABS4(v) { v.vec=_mm_andnot_ps(ABS_MASK, v.vec); }
|
||||
@@ -223,7 +233,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#define NORMALIZE4(v) { SHfloat n=NORM4(v); DIV4(v,n); }
|
||||
|
||||
#define DOT2(v1,v2) (v1.x*v2.x + v1.y*v2.y)
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define DOT4(v1,v2) hsum_ps_sse(_mm_mul_ps(v1.vec,v2.vec))
|
||||
# define DOT4(v1,v2) hsum_ps_sse(_mm_mul_ps(v1.vec,v2.vec))
|
||||
#else
|
||||
@@ -237,7 +247,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
#define ANGLE2N(v1,v2) (SH_ACOS( DOT2(v1,v2) ))
|
||||
|
||||
#define OFFSET2V(v, o, s) { v.x += o.x*s; v.y += o.y*s; }
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define OFFSET4V(v, o, s) { v.vec=_mm_add_ps(v.vec,_mm_mul_ps(o.vec,_mm_set1_ps(s))); }
|
||||
# define OFFSET4V(v, o, s) { v.vec=_mm_add_ps(v.vec,_mm_mul_ps(o.vec,_mm_set1_ps(s))); }
|
||||
#else
|
||||
@@ -249,7 +259,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
* Macros for matrix operations
|
||||
*-----------------------------------------------------*/
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define SETMAT(mat, m00, m01, m02, m10, m11, m12, m20, m21, m22) { \
|
||||
mat.mtx[0] = _mm_set_ps(0,m02,m01,m00); \
|
||||
mat.mtx[1] = _mm_set_ps(0,m12,m11,m10); \
|
||||
@@ -262,7 +272,7 @@ void SHMatrix3x3_dtor(SHMatrix3x3 *m);
|
||||
mat.m[2][0] = m20; mat.m[2][1] = m21; mat.m[2][2] = m22; }
|
||||
#endif
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define SETMATMAT(m1, m2) { \
|
||||
m1.mtx[0] = m2.mtx[0]; \
|
||||
m1.mtx[1] = m2.mtx[1]; \
|
||||
@@ -275,7 +285,7 @@ int i,j; \
|
||||
m1.m[i][j] = m2.m[i][j]; }
|
||||
#endif
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define MULMATS(mat, s) { \
|
||||
mat.mtx[0] = _mm_mul_ps(mat.mtx[0],_mm_set1_ps(s)); \
|
||||
mat.mtx[1] = _mm_mul_ps(mat.mtx[1],_mm_set1_ps(s)); \
|
||||
@@ -288,7 +298,7 @@ int i,j; \
|
||||
mat.m[i][j] *= s; }
|
||||
#endif
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define DIVMATS(mat, s) { \
|
||||
mat.mtx[0] = _mm_mul_ps(mat.mtx[0],_mm_set1_ps(1/s)); \
|
||||
mat.mtx[1] = _mm_mul_ps(mat.mtx[1],_mm_set1_ps(1/s)); \
|
||||
@@ -301,7 +311,7 @@ int i,j; \
|
||||
mat.m[i][j] /= s; }
|
||||
#endif
|
||||
|
||||
#ifdef __SSE__
|
||||
#ifdef SHIVA_USE_SIMD
|
||||
# define MULMATMAT(m2, m1, mout) { \
|
||||
int i,j; \
|
||||
for (i=0;i<4;i++) { \
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "CanvasElement.hxx"
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
#include <simgear/canvas/CanvasEventVisitor.hxx>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "CanvasGroup.hxx"
|
||||
#include "CanvasImage.hxx"
|
||||
#include "CanvasMap.hxx"
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "CanvasImage.hxx"
|
||||
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasMap.hxx"
|
||||
#include "map/geo_node_pair.hxx"
|
||||
#include "map/projection.hxx"
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "CanvasPath.hxx"
|
||||
#include <simgear/scene/util/parse_color.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CanvasText.hxx"
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
#include <simgear/canvas/CanvasSystemAdapter.hxx>
|
||||
@@ -496,18 +497,31 @@ namespace canvas
|
||||
{
|
||||
case LEFT_TO_RIGHT:
|
||||
{
|
||||
osg::Vec2 delta( activefont->getKerning( previous_charcode,
|
||||
charcode,
|
||||
_kerningType ) );
|
||||
#if OSG_VERSION_LESS_THAN(3,5,2)
|
||||
osg::Vec2 delta(activefont->getKerning(previous_charcode,
|
||||
charcode,
|
||||
_kerningType));
|
||||
#else
|
||||
osg::Vec2 delta(activefont->getKerning(_fontSize,
|
||||
previous_charcode,
|
||||
charcode,
|
||||
_kerningType));
|
||||
#endif
|
||||
cursor.x() += delta.x() * wr;
|
||||
cursor.y() += delta.y() * hr;
|
||||
break;
|
||||
}
|
||||
case RIGHT_TO_LEFT:
|
||||
{
|
||||
osg::Vec2 delta( activefont->getKerning( charcode,
|
||||
previous_charcode,
|
||||
_kerningType ) );
|
||||
#if OSG_VERSION_LESS_THAN(3,5,2)
|
||||
osg::Vec2 delta(activefont->getKerning(charcode,
|
||||
previous_charcode,
|
||||
_kerningType));
|
||||
#else
|
||||
osg::Vec2 delta(activefont->getKerning(_fontSize, charcode,
|
||||
previous_charcode,
|
||||
_kerningType));
|
||||
#endif
|
||||
cursor.x() -= delta.x() * wr;
|
||||
cursor.y() -= delta.y() * hr;
|
||||
break;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "CustomEvent.hxx"
|
||||
|
||||
namespace simgear
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "DeviceEvent.hxx"
|
||||
#include <osgGA/GUIEventAdapter>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "KeyboardEvent.hxx"
|
||||
#include "utf8.h"
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "MouseEvent.hxx"
|
||||
#include <osgGA/GUIEventAdapter>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "KeyboardEvent.hxx"
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "BoxLayout.hxx"
|
||||
#include "SpacerItem.hxx"
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "Layout.hxx"
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "LayoutItem.hxx"
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "NasalWidget.hxx"
|
||||
|
||||
#include <simgear/canvas/Canvas.hxx>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "SpacerItem.hxx"
|
||||
|
||||
namespace simgear
|
||||
|
||||
@@ -109,10 +109,13 @@
|
||||
# define SG_UNIX
|
||||
#endif
|
||||
|
||||
#if defined( __GNUC__ )
|
||||
# define DEPRECATED __attribute__ ((deprecated))
|
||||
#ifdef __GNUC__
|
||||
#define SG_DEPRECATED(func) func __attribute__ ((deprecated))
|
||||
#elif defined(_MSC_VER)
|
||||
#define SG_DEPRECATED(func) __declspec(deprecated) func
|
||||
#else
|
||||
# define DEPRECATED
|
||||
#pragma message("WARNING: You need to implement SG_DEPRECATED for this compiler")
|
||||
#define SG_DEPRECATED(func) func
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/debug/BufferedLogCallback.hxx>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
@@ -46,39 +46,7 @@
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
const char* debugClassToString(sgDebugClass c)
|
||||
{
|
||||
switch (c) {
|
||||
case SG_NONE: return "none";
|
||||
case SG_TERRAIN: return "terrain";
|
||||
case SG_ASTRO: return "astro";
|
||||
case SG_FLIGHT: return "flight";
|
||||
case SG_INPUT: return "input";
|
||||
case SG_GL: return "opengl";
|
||||
case SG_VIEW: return "view";
|
||||
case SG_COCKPIT: return "cockpit";
|
||||
case SG_GENERAL: return "general";
|
||||
case SG_MATH: return "math";
|
||||
case SG_EVENT: return "event";
|
||||
case SG_AIRCRAFT: return "aircraft";
|
||||
case SG_AUTOPILOT: return "autopilot";
|
||||
case SG_IO: return "io";
|
||||
case SG_CLIPPER: return "clipper";
|
||||
case SG_NETWORK: return "network";
|
||||
case SG_ATC: return "atc";
|
||||
case SG_NASAL: return "nasal";
|
||||
case SG_INSTR: return "instruments";
|
||||
case SG_SYSTEMS: return "systems";
|
||||
case SG_AI: return "ai";
|
||||
case SG_ENVIRONMENT:return "environment";
|
||||
case SG_SOUND: return "sound";
|
||||
case SG_NAVAID: return "navaid";
|
||||
case SG_GUI: return "gui";
|
||||
case SG_TERRASYNC: return "terrasync";
|
||||
case SG_PARTICLES: return "particles";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -102,6 +70,40 @@ void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
m_class = c;
|
||||
}
|
||||
|
||||
const char* LogCallback::debugClassToString(sgDebugClass c)
|
||||
{
|
||||
switch (c) {
|
||||
case SG_NONE: return "none";
|
||||
case SG_TERRAIN: return "terrain";
|
||||
case SG_ASTRO: return "astro";
|
||||
case SG_FLIGHT: return "flight";
|
||||
case SG_INPUT: return "input";
|
||||
case SG_GL: return "opengl";
|
||||
case SG_VIEW: return "view";
|
||||
case SG_COCKPIT: return "cockpit";
|
||||
case SG_GENERAL: return "general";
|
||||
case SG_MATH: return "math";
|
||||
case SG_EVENT: return "event";
|
||||
case SG_AIRCRAFT: return "aircraft";
|
||||
case SG_AUTOPILOT: return "autopilot";
|
||||
case SG_IO: return "io";
|
||||
case SG_CLIPPER: return "clipper";
|
||||
case SG_NETWORK: return "network";
|
||||
case SG_ATC: return "atc";
|
||||
case SG_NASAL: return "nasal";
|
||||
case SG_INSTR: return "instruments";
|
||||
case SG_SYSTEMS: return "systems";
|
||||
case SG_AI: return "ai";
|
||||
case SG_ENVIRONMENT:return "environment";
|
||||
case SG_SOUND: return "sound";
|
||||
case SG_NAVAID: return "navaid";
|
||||
case SG_GUI: return "gui";
|
||||
case SG_TERRASYNC: return "terrasync";
|
||||
case SG_PARTICLES: return "particles";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -177,7 +179,7 @@ public:
|
||||
|
||||
#endif
|
||||
|
||||
class LogStreamPrivate : public SGThread
|
||||
class logstream::LogStreamPrivate : public SGThread
|
||||
{
|
||||
private:
|
||||
/**
|
||||
@@ -482,52 +484,105 @@ public:
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static logstream* global_logstream = NULL;
|
||||
static LogStreamPrivate* global_privateLogstream = NULL;
|
||||
static std::unique_ptr<logstream> global_logstream;
|
||||
static SGMutex global_logStreamLock;
|
||||
|
||||
logstream::logstream()
|
||||
{
|
||||
global_privateLogstream = new LogStreamPrivate;
|
||||
global_privateLogstream->startLog();
|
||||
d.reset(new LogStreamPrivate);
|
||||
d->startLog();
|
||||
}
|
||||
|
||||
logstream::~logstream()
|
||||
{
|
||||
popup_msgs.clear();
|
||||
global_privateLogstream->stop();
|
||||
delete global_privateLogstream;
|
||||
d->stop();
|
||||
}
|
||||
|
||||
void
|
||||
logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
global_privateLogstream->setLogLevels(c, p);
|
||||
d->setLogLevels(c, p);
|
||||
}
|
||||
|
||||
void logstream::setDeveloperMode(bool devMode)
|
||||
{
|
||||
global_privateLogstream->m_developerMode = devMode;
|
||||
d->m_developerMode = devMode;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
logstream::addCallback(simgear::LogCallback* cb)
|
||||
{
|
||||
global_privateLogstream->addCallback(cb);
|
||||
d->addCallback(cb);
|
||||
}
|
||||
|
||||
void
|
||||
logstream::removeCallback(simgear::LogCallback* cb)
|
||||
{
|
||||
global_privateLogstream->removeCallback(cb);
|
||||
d->removeCallback(cb);
|
||||
}
|
||||
|
||||
void
|
||||
logstream::log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
{
|
||||
global_privateLogstream->log(c, p, fileName, line, msg);
|
||||
d->log(c, p, fileName, line, msg);
|
||||
}
|
||||
|
||||
|
||||
void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns)
|
||||
{
|
||||
unsigned int i, j;
|
||||
char temp[3000], temp1[3000];
|
||||
*temp = 0;
|
||||
|
||||
for (i = 0; i < len + ((len % columns) ? (columns - len % columns) : 0); i++)
|
||||
{
|
||||
if (strlen(temp) > 500) return;
|
||||
/* print offset */
|
||||
if (i % columns == 0)
|
||||
{
|
||||
sprintf(temp1, "0x%06x: ", i);
|
||||
strcat(temp, temp1);
|
||||
}
|
||||
|
||||
/* print hex data */
|
||||
if (i < len)
|
||||
{
|
||||
sprintf(temp1, "%02x ", 0xFF & ((char*)mem)[i]);
|
||||
strcat(temp, temp1);
|
||||
}
|
||||
else /* end of block, just aligning for ASCII dump */
|
||||
{
|
||||
strcat(temp, " ");
|
||||
}
|
||||
|
||||
/* print ASCII dump */
|
||||
if (i % columns == (columns - 1))
|
||||
{
|
||||
for (j = i - (columns - 1); j <= i; j++)
|
||||
{
|
||||
if (j >= len) /* end of block, not really printing */
|
||||
{
|
||||
strcat(temp, " ");
|
||||
}
|
||||
else if (((((char*)mem)[j]) & (char)0x7f) > 32) /* printable char */
|
||||
{
|
||||
char t2[2];
|
||||
t2[0] = 0xFF & ((char*)mem)[j];
|
||||
t2[1] = 0;
|
||||
strcat(temp, t2);
|
||||
}
|
||||
else /* other char */
|
||||
{
|
||||
strcat(temp, ".");
|
||||
}
|
||||
}
|
||||
log(c, p, fileName, line, temp );
|
||||
*temp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -557,31 +612,31 @@ logstream::has_popup()
|
||||
bool
|
||||
logstream::would_log( sgDebugClass c, sgDebugPriority p ) const
|
||||
{
|
||||
return global_privateLogstream->would_log(c,p);
|
||||
return d->would_log(c,p);
|
||||
}
|
||||
|
||||
sgDebugClass
|
||||
logstream::get_log_classes() const
|
||||
{
|
||||
return global_privateLogstream->m_logClass;
|
||||
return d->m_logClass;
|
||||
}
|
||||
|
||||
sgDebugPriority
|
||||
logstream::get_log_priority() const
|
||||
{
|
||||
return global_privateLogstream->m_logPriority;
|
||||
return d->m_logPriority;
|
||||
}
|
||||
|
||||
void
|
||||
logstream::set_log_priority( sgDebugPriority p)
|
||||
{
|
||||
global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
|
||||
d->setLogLevels(d->m_logClass, p);
|
||||
}
|
||||
|
||||
void
|
||||
logstream::set_log_classes( sgDebugClass c)
|
||||
{
|
||||
global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
|
||||
d->setLogLevels(c, d->m_logPriority);
|
||||
}
|
||||
|
||||
|
||||
@@ -597,54 +652,54 @@ sglog()
|
||||
SGGuard<SGMutex> g(global_logStreamLock);
|
||||
|
||||
if( !global_logstream )
|
||||
global_logstream = new logstream();
|
||||
return *global_logstream;
|
||||
global_logstream.reset(new logstream);
|
||||
return *(global_logstream.get());
|
||||
}
|
||||
|
||||
void
|
||||
logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
global_privateLogstream->addCallback(new FileLogCallback(aPath, c, p));
|
||||
d->addCallback(new FileLogCallback(aPath, c, p));
|
||||
}
|
||||
|
||||
void logstream::setStartupLoggingEnabled(bool enabled)
|
||||
{
|
||||
global_privateLogstream->setStartupLoggingEnabled(enabled);
|
||||
d->setStartupLoggingEnabled(enabled);
|
||||
}
|
||||
|
||||
namespace simgear
|
||||
void logstream::requestConsole()
|
||||
{
|
||||
|
||||
void requestConsole()
|
||||
{
|
||||
#if defined (SG_WINDOWS)
|
||||
/*
|
||||
* 2016-09-20(RJH) - Reworked console handling
|
||||
* This is part of the reworked console handling for Win32. This is for building as a Win32 GUI Subsystem where no
|
||||
* console is allocated on launch. If building as a console app then the startup will ensure that a console is created - but
|
||||
* we don't need to handle that.
|
||||
* The new handling is quite simple:
|
||||
* 1. The constructor will ensure that these streams exists. It will attach to the
|
||||
* parent command prompt if started from the command prompt, otherwise the
|
||||
* stdout/stderr will be bound to the NUL device.
|
||||
* 2. with --console a window will always appear regardless of where the process was
|
||||
* started from. Any non redirected streams will be redirected
|
||||
* 3. You cannot use --console and either redirected stream.
|
||||
*
|
||||
* This is called after the Private Log Stream constructor so we need to undo any console that it has attached to.
|
||||
*/
|
||||
const bool stderrAlreadyRedirected = d->m_stderr_isRedirectedAlready;
|
||||
const bool stdoutAlreadyRedirected = d->m_stdout_isRedirectedAlready;
|
||||
|
||||
if (!global_privateLogstream->m_stderr_isRedirectedAlready && !global_privateLogstream->m_stdout_isRedirectedAlready) {
|
||||
/*
|
||||
* 2016-09-20(RJH) - Reworked console handling
|
||||
* This is part of the reworked console handling for Win32. This is for building as a Win32 GUI Subsystem where no
|
||||
* console is allocated on launch. If building as a console app then the startup will ensure that a console is created - but
|
||||
* we don't need to handle that.
|
||||
* The new handling is quite simple:
|
||||
* 1. The constructor will ensure that these streams exists. It will attach to the
|
||||
* parent command prompt if started from the command prompt, otherwise the
|
||||
* stdout/stderr will be bound to the NUL device.
|
||||
* 2. with --console a window will always appear regardless of where the process was
|
||||
* started from. Any non redirected streams will be redirected
|
||||
* 3. You cannot use --console and either redirected stream.
|
||||
*
|
||||
* This is called after the Private Log Stream constructor so we need to undo any console that it has attached to.
|
||||
*/
|
||||
|
||||
if (!stderrAlreadyRedirected && !stdoutAlreadyRedirected) {
|
||||
FreeConsole();
|
||||
if (AllocConsole()) {
|
||||
if (!global_privateLogstream->m_stdout_isRedirectedAlready)
|
||||
if (!stdoutAlreadyRedirected)
|
||||
freopen("conout$", "w", stdout);
|
||||
|
||||
if (!global_privateLogstream->m_stderr_isRedirectedAlready)
|
||||
if (!stderrAlreadyRedirected)
|
||||
freopen("conout$", "w", stderr);
|
||||
|
||||
//http://stackoverflow.com/a/25927081
|
||||
//Clear the error state for each of the C++ standard stream objects.
|
||||
//Clear the error state for each of the C++ standard stream objects.
|
||||
std::wcout.clear();
|
||||
std::cout.clear();
|
||||
std::wcerr.clear();
|
||||
@@ -657,11 +712,19 @@ void requestConsole()
|
||||
}
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
void requestConsole()
|
||||
{
|
||||
sglog().requestConsole();
|
||||
}
|
||||
|
||||
|
||||
void shutdownLogging()
|
||||
{
|
||||
SGGuard<SGMutex> g(global_logStreamLock);
|
||||
delete global_logstream;
|
||||
global_logstream = 0;
|
||||
global_logstream.reset();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
// forward decls
|
||||
class SGPath;
|
||||
|
||||
@@ -49,6 +50,8 @@ protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
|
||||
static const char* debugClassToString(sgDebugClass c);
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
@@ -74,6 +77,14 @@ public:
|
||||
~logstream();
|
||||
|
||||
static void initGlobalLogstream();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Set the global log class and priority level.
|
||||
* @param c debug class
|
||||
@@ -106,6 +117,12 @@ public:
|
||||
void log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg);
|
||||
|
||||
/**
|
||||
* output formatted hex dump of memory block
|
||||
*/
|
||||
void hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns = 16);
|
||||
|
||||
|
||||
/**
|
||||
* support for the SG_POPUP logging class
|
||||
* set the content of the popup message
|
||||
@@ -152,6 +169,10 @@ private:
|
||||
logstream();
|
||||
|
||||
std::vector<std::string> popup_msgs;
|
||||
|
||||
class LogStreamPrivate;
|
||||
|
||||
std::unique_ptr<LogStreamPrivate> d;
|
||||
};
|
||||
|
||||
logstream& sglog();
|
||||
@@ -168,12 +189,14 @@ logstream& sglog();
|
||||
do { if(sglog().would_log(C,P)) { \
|
||||
std::ostringstream os; os << M; \
|
||||
sglog().log(C, P, __FILE__, __LINE__, os.str()); \
|
||||
if (P == SG_POPUP) sglog().popup(os.str()); \
|
||||
if ((P) == SG_POPUP) sglog().popup(os.str()); \
|
||||
} } while(0)
|
||||
#ifdef FG_NDEBUG
|
||||
# define SG_LOG(C,P,M) do { if(P == SG_POPUP) SG_LOGX(C,P,M) } while(0)
|
||||
# define SG_LOG(C,P,M) do { if((P) == SG_POPUP) SG_LOGX(C,P,M) } while(0)
|
||||
# define SG_HEXDUMP(C,P,MEM,LEN)
|
||||
#else
|
||||
# define SG_LOG(C,P,M) SG_LOGX(C,P,M)
|
||||
# define SG_LOG_HEXDUMP(C,P,MEM,LEN) if(sglog().would_log(C,P)) sglog().hexdump(C, P, __FILE__, __LINE__, MEM, LEN)
|
||||
#endif
|
||||
|
||||
#define SG_ORIGIN __FILE__ ":" SG_STRINGIZE(__LINE__)
|
||||
|
||||
14
simgear/embedded_resources/CMakeLists.txt
Normal file
14
simgear/embedded_resources/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
include (SimGearComponent)
|
||||
|
||||
set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx)
|
||||
set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx)
|
||||
|
||||
simgear_component(embedded_resources embedded_resources
|
||||
"${SOURCES}" "${HEADERS}")
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
add_executable(test_embedded_resources embedded_resources_test.cxx)
|
||||
target_link_libraries(test_embedded_resources ${TEST_LIBS})
|
||||
add_test(embedded_resources
|
||||
${EXECUTABLE_OUTPUT_PATH}/test_embedded_resources)
|
||||
endif(ENABLE_TESTS)
|
||||
265
simgear/embedded_resources/EmbeddedResource.cxx
Normal file
265
simgear/embedded_resources/EmbeddedResource.cxx
Normal file
@@ -0,0 +1,265 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResource.cxx --- Class for pointing to/accessing an embedded resource
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
#include <ios> // std::streamsize
|
||||
#include <ostream>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <utility> // std::move()
|
||||
#include <algorithm> // std::min()
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/io/iostreams/CharArrayStream.hxx>
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
|
||||
// Inspired by <http://stackoverflow.com/a/21174979/4756009>
|
||||
template<typename Derived, typename Base>
|
||||
static unique_ptr<Derived> static_unique_ptr_cast(unique_ptr<Base> p)
|
||||
{
|
||||
auto d = static_cast<Derived *>(p.release());
|
||||
return unique_ptr<Derived>(d);
|
||||
}
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * AbstractEmbeddedResource class *
|
||||
// ***************************************************************************
|
||||
|
||||
AbstractEmbeddedResource::AbstractEmbeddedResource(const char *data,
|
||||
std::size_t size)
|
||||
: _data(data),
|
||||
_size(size)
|
||||
{ }
|
||||
|
||||
const char *AbstractEmbeddedResource::rawPtr() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
std::size_t AbstractEmbeddedResource::rawSize() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
string AbstractEmbeddedResource::str() const
|
||||
{
|
||||
if (_size > std::numeric_limits<string::size_type>::max()) {
|
||||
throw sg_range_exception(
|
||||
"Resource too large to fit in an std::string (size: " +
|
||||
std::to_string(_size) + " bytes)");
|
||||
}
|
||||
|
||||
return string(_data, _size);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * RawEmbeddedResource class *
|
||||
// ***************************************************************************
|
||||
|
||||
RawEmbeddedResource::RawEmbeddedResource(const char *data, std::size_t size)
|
||||
: AbstractEmbeddedResource(data, size)
|
||||
{ }
|
||||
|
||||
AbstractEmbeddedResource::CompressionType
|
||||
RawEmbeddedResource::compressionType() const
|
||||
{
|
||||
return AbstractEmbeddedResource::CompressionType::NONE;
|
||||
}
|
||||
|
||||
string RawEmbeddedResource::compressionDescr() const
|
||||
{
|
||||
return string("none");
|
||||
}
|
||||
|
||||
unique_ptr<std::streambuf> RawEmbeddedResource::streambuf() const
|
||||
{
|
||||
// This is a read-only variant of CharArrayStreambuf
|
||||
return unique_ptr<std::streambuf>(
|
||||
new ROCharArrayStreambuf(rawPtr(), rawSize()));
|
||||
}
|
||||
|
||||
unique_ptr<std::istream> RawEmbeddedResource::istream() const
|
||||
{
|
||||
return unique_ptr<std::istream>(new CharArrayIStream(rawPtr(), rawSize()));
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * ZlibEmbeddedResource class *
|
||||
// ***************************************************************************
|
||||
|
||||
ZlibEmbeddedResource::ZlibEmbeddedResource(const char *data,
|
||||
std::size_t compressedSize,
|
||||
std::size_t uncompressedSize)
|
||||
: AbstractEmbeddedResource(data, compressedSize),
|
||||
_uncompressedSize(uncompressedSize),
|
||||
_inBuf(nullptr),
|
||||
_inBufSize(262144), // adjusted below in the constructor body
|
||||
_outBuf(nullptr),
|
||||
_outBufSize(262144),
|
||||
_putbackSize(0) // default for best performance
|
||||
{
|
||||
static_assert(262144 <= std::numeric_limits<std::size_t>::max(),
|
||||
"The std::size_t type is unexpectedly small.");
|
||||
// No need to use an input buffer (where compressed data chunks are put for
|
||||
// zlib to read and decompress) larger than the whole compressed resource!
|
||||
_inBufSize = std::min(rawSize(), _inBufSize);
|
||||
}
|
||||
|
||||
AbstractEmbeddedResource::CompressionType
|
||||
ZlibEmbeddedResource::compressionType() const
|
||||
{ return AbstractEmbeddedResource::CompressionType::ZLIB; }
|
||||
|
||||
string ZlibEmbeddedResource::compressionDescr() const
|
||||
{ return string("zlib"); }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::uncompressedSize() const
|
||||
{ return _uncompressedSize; }
|
||||
|
||||
char* ZlibEmbeddedResource::getInputBufferStart()
|
||||
{ return _inBuf; }
|
||||
|
||||
void ZlibEmbeddedResource::setInputBufferStart(char* inBuf)
|
||||
{ _inBuf = inBuf; }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::getInputBufferSize()
|
||||
{ return _inBufSize; }
|
||||
|
||||
void ZlibEmbeddedResource::setInputBufferSize(std::size_t size)
|
||||
{ _inBufSize = size; }
|
||||
|
||||
char* ZlibEmbeddedResource::getOutputBufferStart()
|
||||
{ return _outBuf; }
|
||||
|
||||
void ZlibEmbeddedResource::setOutputBufferStart(char* outBuf)
|
||||
{ _outBuf = outBuf; }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::getOutputBufferSize()
|
||||
{ return _outBufSize; }
|
||||
|
||||
void ZlibEmbeddedResource::setOutputBufferSize(std::size_t size)
|
||||
{ _outBufSize = size; }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::getPutbackSize()
|
||||
{ return _putbackSize; }
|
||||
|
||||
void ZlibEmbeddedResource::setPutbackSize(std::size_t size)
|
||||
{ _putbackSize = size; }
|
||||
|
||||
unique_ptr<std::streambuf> ZlibEmbeddedResource::streambuf() const
|
||||
{
|
||||
unique_ptr<CharArrayIStream> rawReaderIStream(
|
||||
new CharArrayIStream(rawPtr(), rawSize()));
|
||||
|
||||
return unique_ptr<std::streambuf>(
|
||||
new ZlibDecompressorIStreambuf(
|
||||
std::move(rawReaderIStream),
|
||||
SGPath(), // rawReaderIStream isn't bound to a file
|
||||
ZLibCompressionFormat::ZLIB,
|
||||
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
|
||||
}
|
||||
|
||||
unique_ptr<std::istream> ZlibEmbeddedResource::istream() const
|
||||
{
|
||||
unique_ptr<CharArrayIStream> rawReaderIStream(
|
||||
new CharArrayIStream(rawPtr(), rawSize()));
|
||||
|
||||
return unique_ptr<std::istream>(
|
||||
new ZlibDecompressorIStream(
|
||||
std::move(rawReaderIStream),
|
||||
SGPath(), // rawReaderIStream isn't bound to a file
|
||||
ZLibCompressionFormat::ZLIB,
|
||||
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
|
||||
}
|
||||
|
||||
std::string ZlibEmbeddedResource::str() const
|
||||
{
|
||||
static constexpr std::size_t bufSize = 65536;
|
||||
static_assert(bufSize <= std::numeric_limits<std::streamsize>::max(),
|
||||
"Type std::streamsize is unexpectedly small");
|
||||
static_assert(bufSize <= std::numeric_limits<string::size_type>::max(),
|
||||
"Type std::string::size_type is unexpectedly small");
|
||||
unique_ptr<char[]> buf(new char[bufSize]);
|
||||
|
||||
auto decompressor =
|
||||
static_unique_ptr_cast<ZlibDecompressorIStream>(istream());
|
||||
std::streamsize nbCharsRead;
|
||||
string result;
|
||||
|
||||
if (_uncompressedSize > std::numeric_limits<string::size_type>::max()) {
|
||||
throw sg_range_exception(
|
||||
"Resource too large to fit in an std::string (uncompressed size: "
|
||||
+ std::to_string(_uncompressedSize) + " bytes)");
|
||||
} else {
|
||||
result.reserve(static_cast<string::size_type>(_uncompressedSize));
|
||||
}
|
||||
|
||||
do {
|
||||
decompressor->read(buf.get(), bufSize);
|
||||
nbCharsRead = decompressor->gcount();
|
||||
|
||||
if (nbCharsRead > 0) {
|
||||
result.append(buf.get(), nbCharsRead);
|
||||
}
|
||||
} while (*decompressor);
|
||||
|
||||
// decompressor->fail() would *not* indicate an error, due to the semantics
|
||||
// of std::istream::read().
|
||||
if (decompressor->bad()) {
|
||||
throw sg_io_exception("Error while extracting a compressed resource");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * Stream insertion operators *
|
||||
// ***************************************************************************
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const RawEmbeddedResource& resource)
|
||||
{ // This won't escape double quotes, backslashes, etc. in resource.str().
|
||||
return os << "RawEmbeddedResource:\n"
|
||||
" compressionType = \"" << resource.compressionDescr() << "\"\n"
|
||||
" rawPtr = " << (void*) resource.rawPtr() << "\n"
|
||||
" rawSize = " << resource.rawSize();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const ZlibEmbeddedResource& resource)
|
||||
{ // This won't escape double quotes, backslashes, etc. in resource.str().
|
||||
return os << "ZlibEmbeddedResource:\n"
|
||||
" compressionType = \"" << resource.compressionDescr() << "\"\n"
|
||||
" rawPtr = " << (void*) resource.rawPtr() << "\n"
|
||||
" rawSize = " << resource.rawSize() << "\n"
|
||||
" uncompressedSize = " << resource.uncompressedSize();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
163
simgear/embedded_resources/EmbeddedResource.hxx
Normal file
163
simgear/embedded_resources/EmbeddedResource.hxx
Normal file
@@ -0,0 +1,163 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResource.hxx --- Class for pointing to/accessing an embedded resource
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_EMBEDDEDRESOURCE_HXX
|
||||
#define FG_EMBEDDEDRESOURCE_HXX
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// Abstract base class for embedded resources
|
||||
class AbstractEmbeddedResource
|
||||
{
|
||||
public:
|
||||
enum class CompressionType {
|
||||
NONE = 0,
|
||||
ZLIB
|
||||
};
|
||||
|
||||
// Constructor.
|
||||
//
|
||||
// 'data' and 'size' indicate the resource contents. There is no requirement
|
||||
// of null-termination, including for text data (given how
|
||||
// EmbeddedResourceManager::getString() works, including a null terminator
|
||||
// for text contents is actually counter-productive). The data may be of
|
||||
// arbitrary type and size: binary, text, whatever. The constructed object
|
||||
// (for derived classes since this one is abstract) does *not* hold a copy
|
||||
// of the data, it just keeps a pointer to it and provides methods to access
|
||||
// it. The data must therefore remain available as long as the object is in
|
||||
// use---this class was designed for use with data stored in static
|
||||
// variables.
|
||||
explicit AbstractEmbeddedResource(const char *data, std::size_t size);
|
||||
AbstractEmbeddedResource(const AbstractEmbeddedResource&) = default;
|
||||
AbstractEmbeddedResource(AbstractEmbeddedResource&&) = default;
|
||||
AbstractEmbeddedResource& operator=(const AbstractEmbeddedResource&) = default;
|
||||
AbstractEmbeddedResource& operator=(AbstractEmbeddedResource&&) = default;
|
||||
virtual ~AbstractEmbeddedResource() = default;
|
||||
|
||||
// Return the pointer to beginning-of-resource contents---the same that was
|
||||
// passed to the constructor.
|
||||
const char *rawPtr() const;
|
||||
// Return the resource size, as passed to the constructor. For a compressed
|
||||
// resource, this is the compressed size; such resources provide an
|
||||
// additional uncompressedSize() method.
|
||||
std::size_t rawSize() const;
|
||||
|
||||
// Return an std::string object containing a copy of the resource contents.
|
||||
// For a compressed resource, this is the data obtained after decompression.
|
||||
virtual std::string str() const;
|
||||
// Return an std::streambuf instance providing read-only access to the
|
||||
// resource contents (in uncompressed form for compressed resources). This
|
||||
// allows memory-friendly access to large resources by enabling incremental
|
||||
// processing with transparent decompression for compressed resources.
|
||||
virtual std::unique_ptr<std::streambuf> streambuf() const = 0;
|
||||
// Return an std::istream instance providing read-only access to the
|
||||
// resource contents (in uncompressed form for compressed resources).
|
||||
//
|
||||
// The same remark as for streambuf() applies. std::istream is simply a
|
||||
// higher-level interface than std::streambuf, otherwise both allow the same
|
||||
// kind of processing.
|
||||
virtual std::unique_ptr<std::istream> istream() const = 0;
|
||||
|
||||
// Return the resource compression type.
|
||||
virtual CompressionType compressionType() const = 0;
|
||||
// Return a string description of the resource compression type. Examples:
|
||||
// "none", "zlib".
|
||||
virtual std::string compressionDescr() const = 0;
|
||||
|
||||
private:
|
||||
// Pointer to the start of resource contents
|
||||
const char *_data;
|
||||
// Size of resource contents, in bytes
|
||||
std::size_t _size;
|
||||
};
|
||||
|
||||
// Class to describe an uncompressed resource. See AbstractEmbeddedResource.
|
||||
class RawEmbeddedResource : public AbstractEmbeddedResource
|
||||
{
|
||||
public:
|
||||
explicit RawEmbeddedResource(const char *data, std::size_t size);
|
||||
|
||||
AbstractEmbeddedResource::CompressionType compressionType() const override;
|
||||
std::string compressionDescr() const override;
|
||||
|
||||
// The str() method is inherited from AbstractEmbeddedResource
|
||||
std::unique_ptr<std::streambuf> streambuf() const override;
|
||||
std::unique_ptr<std::istream> istream() const override;
|
||||
};
|
||||
|
||||
// Class to describe a zlib-compressed resource.
|
||||
//
|
||||
// Instances of this class point to resource contents stored in the stream
|
||||
// format documented in RFC 1950.
|
||||
class ZlibEmbeddedResource : public AbstractEmbeddedResource
|
||||
{
|
||||
public:
|
||||
explicit ZlibEmbeddedResource(const char *data, std::size_t compressedSize,
|
||||
std::size_t uncompressedSize);
|
||||
|
||||
AbstractEmbeddedResource::CompressionType compressionType() const override;
|
||||
std::string compressionDescr() const override;
|
||||
// Return the resource uncompressed size, in bytes.
|
||||
std::size_t uncompressedSize() const;
|
||||
|
||||
std::string str() const override;
|
||||
std::unique_ptr<std::streambuf> streambuf() const override;
|
||||
std::unique_ptr<std::istream> istream() const override;
|
||||
|
||||
// Getters and setters for parameters used in streambuf() and istream().
|
||||
// Calling any of the setters affects the subsequent streambuf() and
|
||||
// istream() calls.
|
||||
char* getInputBufferStart();
|
||||
void setInputBufferStart(char* inBuf);
|
||||
std::size_t getInputBufferSize();
|
||||
void setInputBufferSize(std::size_t size);
|
||||
char* getOutputBufferStart();
|
||||
void setOutputBufferStart(char* outBuf);
|
||||
std::size_t getOutputBufferSize();
|
||||
void setOutputBufferSize(std::size_t size);
|
||||
std::size_t getPutbackSize();
|
||||
void setPutbackSize(std::size_t size);
|
||||
|
||||
private:
|
||||
std::size_t _uncompressedSize;
|
||||
char* _inBuf;
|
||||
std::size_t _inBufSize;
|
||||
char* _outBuf;
|
||||
std::size_t _outBufSize;
|
||||
std::size_t _putbackSize;
|
||||
};
|
||||
|
||||
// These functions are essentially intended for troubleshooting purposes.
|
||||
std::ostream& operator<<(std::ostream&, const RawEmbeddedResource&);
|
||||
std::ostream& operator<<(std::ostream&, const ZlibEmbeddedResource&);
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of FG_EMBEDDEDRESOURCE_HXX
|
||||
235
simgear/embedded_resources/EmbeddedResourceManager.cxx
Normal file
235
simgear/embedded_resources/EmbeddedResourceManager.cxx
Normal file
@@ -0,0 +1,235 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResourceManager.cxx --- Manager class for resources embedded in an
|
||||
// executable
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility> // std::move()
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
#include "EmbeddedResourceManager.hxx"
|
||||
#include "EmbeddedResourceManager_private.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::shared_ptr;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
static unique_ptr<EmbeddedResourceManager> staticInstance;
|
||||
|
||||
// ***************************************************************************
|
||||
// * EmbeddedResourceManager::Impl *
|
||||
// ***************************************************************************
|
||||
EmbeddedResourceManager::Impl::Impl()
|
||||
: dirty(true)
|
||||
{ }
|
||||
|
||||
void
|
||||
EmbeddedResourceManager::Impl::rehash()
|
||||
{
|
||||
// Update the list of resource pools to search when looking up a resource.
|
||||
// This allows to optimize resource lookup: no need to parse, split and hash
|
||||
// the same locale string every time to find the corresponding resource
|
||||
// pools.
|
||||
poolSearchList = listOfResourcePoolsToSearch(selectedLocale);
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::Impl::getLocale() const
|
||||
{
|
||||
return selectedLocale;
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::Impl::selectLocale(const std::string& locale)
|
||||
{
|
||||
string previousLocale = std::move(selectedLocale);
|
||||
selectedLocale = locale;
|
||||
dirty = true;
|
||||
|
||||
return previousLocale;
|
||||
}
|
||||
|
||||
// Static method
|
||||
std::vector<string>
|
||||
EmbeddedResourceManager::Impl::localesSearchList(const string& locale)
|
||||
{
|
||||
std::vector<string> result;
|
||||
|
||||
if (locale.empty()) {
|
||||
result.push_back(string()); // only the default locale
|
||||
} else {
|
||||
std::size_t sepIdx = locale.find_first_of('_');
|
||||
|
||||
if (sepIdx == string::npos) {
|
||||
// Try the given “locale” first (e.g., fr), then the default locale
|
||||
result = std::vector<string>({locale, string()});
|
||||
} else {
|
||||
string langCode = locale.substr(0, sepIdx);
|
||||
// Try the given “locale” first (e.g., fr_FR), then the language code
|
||||
// (e.g., fr) and finally the default locale
|
||||
result = std::vector<string>({locale, langCode, string()});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto
|
||||
EmbeddedResourceManager::Impl::listOfResourcePoolsToSearch(
|
||||
const string& locale) const
|
||||
-> std::vector< shared_ptr<ResourcePool> >
|
||||
{
|
||||
std::vector<string> searchedLocales = localesSearchList(locale);
|
||||
std::vector< shared_ptr<ResourcePool> > result;
|
||||
|
||||
for (const string& loc: searchedLocales) {
|
||||
auto poolPtrIt = localeToResourcePoolMap.find(loc);
|
||||
// Don't store pointers to empty resource pools in 'result'. This
|
||||
// optimizes resource fetching a little bit, but requires that all
|
||||
// resources are added before this method is called.
|
||||
if (poolPtrIt != localeToResourcePoolMap.end()) {
|
||||
// Copy a shared_ptr<ResourcePool>
|
||||
result.push_back(poolPtrIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Static method
|
||||
shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::Impl::lookupResourceInPools(
|
||||
const string& virtualPath,
|
||||
const std::vector< shared_ptr<ResourcePool> >& aPoolSearchList)
|
||||
{
|
||||
// Search the provided resource pools in proper order. For instance, the one
|
||||
// for 'fr_FR', then the one for 'fr' and finally the one for the default
|
||||
// locale. Return the first resource found in one of these pools.
|
||||
for (const shared_ptr<ResourcePool>& poolPtr: aPoolSearchList) {
|
||||
auto resourcePtrIt = poolPtr->find(virtualPath);
|
||||
|
||||
if (resourcePtrIt != poolPtr->end()) {
|
||||
// Copy a shared_ptr<const AbstractEmbeddedResource>
|
||||
return resourcePtrIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
return shared_ptr<const AbstractEmbeddedResource>(); // null shared_ptr object
|
||||
}
|
||||
|
||||
void
|
||||
EmbeddedResourceManager::Impl::addResource(
|
||||
const string& virtualPath,
|
||||
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const string& locale)
|
||||
{
|
||||
// Find the resource pool corresponding to the specified locale
|
||||
shared_ptr<ResourcePool>& resPoolPtr = localeToResourcePoolMap[locale];
|
||||
if (!resPoolPtr) {
|
||||
resPoolPtr.reset(new ResourcePool());
|
||||
}
|
||||
|
||||
auto emplaceRetval = resPoolPtr->emplace(virtualPath, std::move(resourcePtr));
|
||||
|
||||
if (!emplaceRetval.second) {
|
||||
const string localeDescr =
|
||||
(locale.empty()) ? "the default locale" : "locale '" + locale + "'";
|
||||
throw sg_error(
|
||||
"Virtual path already in use for " + localeDescr +
|
||||
" in the EmbeddedResourceManager: '" + virtualPath + "'");
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * EmbeddedResourceManager *
|
||||
// ***************************************************************************
|
||||
EmbeddedResourceManager::EmbeddedResourceManager()
|
||||
: p(unique_ptr<Impl>(new Impl))
|
||||
{ }
|
||||
|
||||
const unique_ptr<EmbeddedResourceManager>&
|
||||
EmbeddedResourceManager::createInstance()
|
||||
{
|
||||
staticInstance.reset(new EmbeddedResourceManager);
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
const unique_ptr<EmbeddedResourceManager>&
|
||||
EmbeddedResourceManager::instance()
|
||||
{
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::getLocale() const
|
||||
{
|
||||
return p->getLocale();
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::selectLocale(const std::string& locale)
|
||||
{
|
||||
return p->selectLocale(locale);
|
||||
}
|
||||
|
||||
void
|
||||
EmbeddedResourceManager::addResource(
|
||||
const string& virtualPath,
|
||||
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const string& locale)
|
||||
{
|
||||
p->addResource(virtualPath, std::move(resourcePtr), locale);
|
||||
}
|
||||
|
||||
shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath) const
|
||||
{
|
||||
if (p->dirty) {
|
||||
p->rehash(); // update p->poolSearchList
|
||||
}
|
||||
|
||||
// Use the selected locale
|
||||
return p->lookupResourceInPools(virtualPath, p->poolSearchList);
|
||||
}
|
||||
|
||||
shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath,
|
||||
const string& locale) const
|
||||
{
|
||||
// In this overload, we don't use the cached list of pools
|
||||
// (p->poolSearchList), therefore there is no need to check the 'dirty' flag
|
||||
// or to rehash().
|
||||
return p->lookupResourceInPools(virtualPath,
|
||||
p->listOfResourcePoolsToSearch(locale));
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
204
simgear/embedded_resources/EmbeddedResourceManager.hxx
Normal file
204
simgear/embedded_resources/EmbeddedResourceManager.hxx
Normal file
@@ -0,0 +1,204 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResourceManager.hxx --- Manager class for resources embedded in an
|
||||
// executable
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_EMBEDDEDRESOURCEMANAGER_HXX
|
||||
#define FG_EMBEDDEDRESOURCEMANAGER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <memory> // std::unique_ptr, std::shared_ptr
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <utility> // std::forward()
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class EmbeddedResourceManager
|
||||
{
|
||||
public:
|
||||
EmbeddedResourceManager(const EmbeddedResourceManager&) = delete;
|
||||
EmbeddedResourceManager& operator=(const EmbeddedResourceManager&) = delete;
|
||||
EmbeddedResourceManager(EmbeddedResourceManager&&) = delete;
|
||||
EmbeddedResourceManager& operator=(EmbeddedResourceManager&&) = delete;
|
||||
// The instance is created by createInstance() -> private constructor
|
||||
// but it should be deleted by its owning std::unique_ptr -> public destructor
|
||||
~EmbeddedResourceManager() = default;
|
||||
|
||||
// Static creator
|
||||
static const std::unique_ptr<EmbeddedResourceManager>& createInstance();
|
||||
|
||||
// Singleton accessor
|
||||
static const std::unique_ptr<EmbeddedResourceManager>& instance();
|
||||
|
||||
// Return the currently-selected “locale”[*] for resource fetching.
|
||||
//
|
||||
// [*] For instance: std::string("") for the default locale
|
||||
// std::string("fr") for French
|
||||
// std::string("fr_FR") for French from France
|
||||
std::string getLocale() const;
|
||||
// Select the locale for which resources will be returned in the future, for
|
||||
// the getResourceOrNullPtr(), getResource(), getString(), getStreambuf()
|
||||
// and getIStream() overloads that don't have a 'locale' parameter.
|
||||
// May be called several times. Return the previously-selected locale.
|
||||
//
|
||||
// If you just want to fetch one or two resources in a particular “locale”
|
||||
// (language), it is simpler to use an overload of one of the
|
||||
// getResourceOrNullPtr(), getResource(), ..., getIStream() methods that has
|
||||
// a 'locale' parameter.
|
||||
std::string selectLocale(const std::string& locale);
|
||||
|
||||
// Add a resource for the specified locale to the embedded resource manager.
|
||||
// This method acts as a sink for its second argument (the std::unique_ptr
|
||||
// typically has to be std::move()d). If 'locale' is empty, the resource is
|
||||
// added for the default locale.
|
||||
void addResource(const std::string& virtualPath,
|
||||
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const std::string& locale = std::string());
|
||||
|
||||
// Get access to a resource.
|
||||
//
|
||||
// Fetch the resource for the selected locale (cf. selectLocale()), with
|
||||
// fallback behavior[1]. If no resource is found for the given
|
||||
// 'virtualPath', return a null
|
||||
// std::shared_ptr<const AbstractEmbeddedResource>.
|
||||
//
|
||||
// [1] This means that for instance, if the selected locale is 'es_ES', the
|
||||
// resource is first looked up for the 'es_ES' “locale”; then, if not
|
||||
// found, for 'es'; and finally, if still not found, for the default
|
||||
// locale ''.
|
||||
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
|
||||
const std::string& virtualPath) const;
|
||||
// Same as the previous overload, except the resource is fetched for the
|
||||
// specified locale (with fallback behavior) instead of for the selected
|
||||
// locale. Use an empty 'locale' parameter to fetch the resource for the
|
||||
// default locale.
|
||||
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
|
||||
const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
|
||||
// Same overloads as for getResourceOrNullPtr(), except that if the resource
|
||||
// isn't found, then an sg_exception is raised. These methods never return
|
||||
// a null or empty std::shared_ptr<const AbstractEmbeddedResource>.
|
||||
template <typename ...Args>
|
||||
std::shared_ptr<const AbstractEmbeddedResource> getResource(
|
||||
const std::string& virtualPath, Args&& ...args) const
|
||||
{
|
||||
const auto resPtr = getResourceOrNullPtr(virtualPath,
|
||||
std::forward<Args>(args)...);
|
||||
|
||||
if (!resPtr) {
|
||||
throw sg_exception("No embedded resource found at virtual path '" +
|
||||
virtualPath + "'");
|
||||
}
|
||||
|
||||
return resPtr;
|
||||
}
|
||||
|
||||
// Get a resource contents in the form of an std::string. Raise an
|
||||
// sg_exception if no resource is found for the specified 'virtualPath'.
|
||||
//
|
||||
// The returned std::string is a copy of the resource contents (possibly
|
||||
// transparently decompressed, cf. simgear::ZlibEmbeddedResource).
|
||||
template <typename ...Args>
|
||||
std::string getString(const std::string& virtualPath, Args&& ...args) const
|
||||
{
|
||||
return getResource(virtualPath, std::forward<Args>(args)...)->str();
|
||||
}
|
||||
|
||||
// Get access to a resource via an std::streambuf instance. Raise an
|
||||
// sg_exception if no resource is found for the specified 'virtualPath'.
|
||||
//
|
||||
// This allows one to incrementally process the resource contents without
|
||||
// ever making a copy of it (including incremental, transparent
|
||||
// decompression if the resource happens to be compressed---cf.
|
||||
// simgear::ZlibEmbeddedResource).
|
||||
template <typename ...Args>
|
||||
std::unique_ptr<std::streambuf> getStreambuf(const std::string& virtualPath,
|
||||
Args&& ...args) const
|
||||
{
|
||||
return getResource(virtualPath, std::forward<Args>(args)...)->streambuf();
|
||||
}
|
||||
|
||||
// Get access to a resource via an std::istream instance. Raise an
|
||||
// sg_exception if no resource is found for the specified 'virtualPath'.
|
||||
//
|
||||
// The same remarks made for getStreambuf() apply here too.
|
||||
template <typename ...Args>
|
||||
std::unique_ptr<std::istream> getIStream(const std::string& virtualPath,
|
||||
Args&& ...args) const
|
||||
{
|
||||
return getResource(virtualPath, std::forward<Args>(args)...)->istream();
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor called from createInstance() only
|
||||
explicit EmbeddedResourceManager();
|
||||
|
||||
class Impl;
|
||||
const std::unique_ptr<Impl> p; // Pimpl idiom
|
||||
};
|
||||
|
||||
// Explicit template instantiations
|
||||
template
|
||||
std::shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResource(const std::string& virtualPath) const;
|
||||
|
||||
template
|
||||
std::string
|
||||
EmbeddedResourceManager::getString(const std::string& virtualPath) const;
|
||||
|
||||
template
|
||||
std::unique_ptr<std::streambuf>
|
||||
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath) const;
|
||||
|
||||
template
|
||||
std::unique_ptr<std::istream>
|
||||
EmbeddedResourceManager::getIStream(const std::string& virtualPath) const;
|
||||
|
||||
// MSVC doesn't recognize these as template instantiations of what we defined
|
||||
// above (this seems to be with “Visual Studio 14 2015”), therefore only
|
||||
// include them with other compilers.
|
||||
#ifndef _MSC_VER
|
||||
template
|
||||
std::shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResource(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
template
|
||||
std::string
|
||||
EmbeddedResourceManager::getString(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
template
|
||||
std::unique_ptr<std::streambuf>
|
||||
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
template
|
||||
std::unique_ptr<std::istream>
|
||||
EmbeddedResourceManager::getIStream(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
#endif // #ifndef _MSC_VER
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of FG_EMBEDDEDRESOURCEMANAGER_HXX
|
||||
102
simgear/embedded_resources/EmbeddedResourceManager_private.hxx
Normal file
102
simgear/embedded_resources/EmbeddedResourceManager_private.hxx
Normal file
@@ -0,0 +1,102 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResourceManager_private.hxx --- Private implementation class for
|
||||
// SimGear's EmbeddedResourceManager
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
|
||||
#define FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
|
||||
|
||||
#include <string>
|
||||
#include <memory> // std::unique_ptr, std::shared_ptr
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "EmbeddedResource.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class EmbeddedResourceManager::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl();
|
||||
|
||||
// Each “locale” for which addResource() has been used has an associated
|
||||
// resource pool, that is a sort of directory of all resources declared in
|
||||
// this locale. The resource pool for a given locale (e.g., 'fr' or 'de_DE')
|
||||
// maps resource virtual paths to the corresponding resource descriptors
|
||||
// (via std::shared_ptr<const AbstractEmbeddedResource> instances).
|
||||
//
|
||||
// Note: for optimal lookup performance, a tree would probably be better,
|
||||
// since the expected use for each key here is to store a virtual
|
||||
// path. But such an optimization is likely unneeded in most cases.
|
||||
typedef std::unordered_map< std::string,
|
||||
std::shared_ptr<const AbstractEmbeddedResource> >
|
||||
ResourcePool;
|
||||
|
||||
// Return the list of “locales” to scan to implement fallback behavior when
|
||||
// fetching a resource for the specified locale. This list will be searched
|
||||
// from left to right. Examples:
|
||||
//
|
||||
// "" -> [""]
|
||||
// "fr" -> ["fr", ""]
|
||||
// "fr_FR" -> ["fr_FR", "fr", ""]
|
||||
static std::vector<std::string> localesSearchList(const std::string& locale);
|
||||
// Same as localesSearchList(), except it returns the resource pools instead
|
||||
// of the “locale” strings, and only those pools that are not empty.
|
||||
std::vector< std::shared_ptr<ResourcePool> > listOfResourcePoolsToSearch(
|
||||
const std::string& locale) const;
|
||||
// Look up, in each of the pools referred to by 'poolSearchList', the
|
||||
// resource associated to 'virtualPath'. Return the first match.
|
||||
static std::shared_ptr<const AbstractEmbeddedResource> lookupResourceInPools(
|
||||
const std::string& virtualPath,
|
||||
const std::vector< std::shared_ptr<ResourcePool> >& poolSearchList);
|
||||
|
||||
// Recompute p->poolSearchList. This method is automatically called whenever
|
||||
// needed (lazily), so it doesn't need to be part of the public interface.
|
||||
void rehash();
|
||||
|
||||
// Implement the corresponding EmbeddedResourceManager public methods
|
||||
std::string getLocale() const;
|
||||
std::string selectLocale(const std::string& locale);
|
||||
|
||||
// Ditto
|
||||
void addResource(const std::string& virtualPath,
|
||||
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const std::string& locale);
|
||||
|
||||
std::string selectedLocale;
|
||||
// Each call to rehash() updates this member to contain precisely the
|
||||
// (ordered) list of pools to search for a resource in the selected
|
||||
// “locale”. This allows relatively cheap resource lookups, assuming the
|
||||
// desired “locale” doesn't change all the time.
|
||||
std::vector< std::shared_ptr<ResourcePool> > poolSearchList;
|
||||
// Indicate whether 'poolSearchList' must be updated (i.e., resources have
|
||||
// been added or the selected locale was changed without rehash() being
|
||||
// called afterwards).
|
||||
bool dirty;
|
||||
|
||||
// Maps each “locale name” to the corresponding resource pool.
|
||||
std::unordered_map< std::string,
|
||||
std::shared_ptr<ResourcePool> > localeToResourcePoolMap;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
|
||||
412
simgear/embedded_resources/embedded_resources_test.cxx
Normal file
412
simgear/embedded_resources/embedded_resources_test.cxx
Normal file
@@ -0,0 +1,412 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// embedded_resources_test.cxx --- Automated tests for the embedded resources
|
||||
// system in SimGear
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <ios> // std::streamsize
|
||||
#include <iostream> // std::cout (used for progress info)
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <sstream>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/io/iostreams/CharArrayStream.hxx>
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
#include "EmbeddedResourceManager.hxx"
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
using std::shared_ptr;
|
||||
using simgear::AbstractEmbeddedResource;
|
||||
using simgear::RawEmbeddedResource;
|
||||
using simgear::ZlibEmbeddedResource;
|
||||
using simgear::EmbeddedResourceManager;
|
||||
|
||||
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
|
||||
|
||||
// Safely convert a non-negative std::streamsize into an std::size_t. If
|
||||
// impossible, bail out.
|
||||
std::size_t streamsizeToSize_t(std::streamsize n)
|
||||
{
|
||||
SG_CHECK_GE(n, 0);
|
||||
SG_CHECK_LE(static_cast<uStreamSize>(n),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
|
||||
return static_cast<std::size_t>(n);
|
||||
}
|
||||
|
||||
// This array is null-terminated, but we'll declare the resource size as
|
||||
// sizeof(res1Array) - 1 so that the null char is *not* part of it. This
|
||||
// way allows one to treat text and binary resources exactly the same way,
|
||||
// with the conversion to std::string via a simple
|
||||
// std::string(res1Array, resourceSize) not producing a bizarre std::string
|
||||
// instance whose last character would be '\0' (followed in memory by the same
|
||||
// '\0' used as C-style string terminator this time!).
|
||||
static const char res1Array[] = "This is a simple embedded resource test.";
|
||||
static const char res1frArray[] = "Ceci est un petit test de ressource "
|
||||
"embarquée.";
|
||||
static const char res1fr_FRArray[] = "Ceci est un petit test de ressource "
|
||||
"embarquée (variante fr_FR).";
|
||||
static const string lipsum = "\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque congue ornare\n\
|
||||
congue. Mauris mollis est et porttitor condimentum. Vivamus laoreet blandit\n\
|
||||
odio eget consectetur. Etiam quis magna eu enim luctus pretium. In et\n\
|
||||
tristique nunc, non efficitur metus. Nullam efficitur tristique velit.\n\
|
||||
Praesent et luctus nunc. Mauris eros eros, rutrum at molestie quis, egestas et\n\
|
||||
lorem. Ut nulla turpis, eleifend sed mauris ac, faucibus molestie nulla.\n\
|
||||
Quisque viverra vel turpis nec efficitur. Proin non rutrum velit. Nam sodales\n\
|
||||
metus felis, eu pharetra velit posuere ut.";
|
||||
// Should be enough to store the compressed lipsum (320 bytes are required
|
||||
// with zlib 1.2.8, keeping some room to account for possible future format
|
||||
// changes in the zlib output...). In any case, there is no risk of buffer
|
||||
// overflow, because simgear::CharArrayOStream prevents this by design.
|
||||
static char res2Array[350];
|
||||
static const char res2frArray[] = "Un lorem ipsum un peu plus court...";
|
||||
|
||||
|
||||
// Read data from a string and write it in compressed form to the specified
|
||||
// buffer.
|
||||
std::size_t writeCompressedDataToBuffer(const string& inputString,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize)
|
||||
{
|
||||
simgear::CharArrayOStream res2Writer(outBuf, outBufSize);
|
||||
std::istringstream iss(inputString);
|
||||
simgear::ZlibCompressorIStream compressor(
|
||||
iss,
|
||||
SGPath(), /* no associated file */
|
||||
9 /* highest compression level */);
|
||||
static constexpr std::size_t bufSize = 1024;
|
||||
unique_ptr<char[]> buf(new char[bufSize]);
|
||||
std::size_t res2Size = 0;
|
||||
|
||||
do {
|
||||
compressor.read(buf.get(), bufSize);
|
||||
std::streamsize nBytes = compressor.gcount();
|
||||
if (nBytes > 0) { // at least one char could be read
|
||||
res2Writer.write(buf.get(), nBytes);
|
||||
res2Size += nBytes;
|
||||
}
|
||||
} while (compressor && res2Writer);
|
||||
|
||||
SG_VERIFY(compressor.eof()); // all the compressed data has been read
|
||||
// This would fail (among other causes) if the output buffer were too small
|
||||
// to hold all of the compressed data.
|
||||
SG_VERIFY(res2Writer);
|
||||
|
||||
return res2Size;
|
||||
}
|
||||
|
||||
void initResources()
|
||||
{
|
||||
cout << "Creating the EmbeddedResourceManager instance and adding a few "
|
||||
"resources to it" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::createInstance();
|
||||
|
||||
// The resource will *not* consider the null terminator to be in.
|
||||
unique_ptr<const RawEmbeddedResource> res1(
|
||||
new RawEmbeddedResource(res1Array, sizeof(res1Array) - 1));
|
||||
resMgr->addResource("/path/to/resource1", std::move(res1));
|
||||
|
||||
unique_ptr<const RawEmbeddedResource> res1fr(
|
||||
new RawEmbeddedResource(res1frArray, sizeof(res1frArray) - 1));
|
||||
resMgr->addResource("/path/to/resource1", std::move(res1fr), "fr");
|
||||
|
||||
unique_ptr<const RawEmbeddedResource> res1fr_FR(
|
||||
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray) - 1));
|
||||
resMgr->addResource("/path/to/resource1", std::move(res1fr_FR), "fr_FR");
|
||||
|
||||
// Write the contents of 'lipsum' in compressed form to the 'res2Array'
|
||||
// static buffer.
|
||||
std::size_t res2Size = writeCompressedDataToBuffer(lipsum, res2Array,
|
||||
sizeof(res2Array));
|
||||
// Now we have a compressed resource to work with, plus the corresponding
|
||||
// uncompressed output -> perfect for tests!
|
||||
unique_ptr<const ZlibEmbeddedResource> res2(
|
||||
new ZlibEmbeddedResource(res2Array, res2Size, lipsum.size()));
|
||||
resMgr->addResource("/path/to/resource2", std::move(res2));
|
||||
|
||||
unique_ptr<const RawEmbeddedResource> res2fr(
|
||||
new RawEmbeddedResource(res2frArray, sizeof(res2frArray) - 1));
|
||||
resMgr->addResource("/path/to/resource2", std::move(res2fr), "fr");
|
||||
|
||||
// Explicitly select the default locale (typically, English). This is for
|
||||
// clarity, but isn't required.
|
||||
resMgr->selectLocale("");
|
||||
}
|
||||
|
||||
// Auxiliary function for test_RawEmbeddedResource()
|
||||
void auxTest_RawEmbeddedResource_streambuf()
|
||||
{
|
||||
cout << "Testing EmbeddedResourceManager::getStreambuf()" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
unique_ptr<std::streambuf> sbuf(resMgr->getStreambuf("/path/to/resource1"));
|
||||
// Just to show an efficient algorithm. For real applications, use larger
|
||||
// buffer sizes!
|
||||
static constexpr std::size_t bufSize = 4;
|
||||
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
|
||||
std::streamsize nbCharsRead;
|
||||
string result;
|
||||
|
||||
do {
|
||||
nbCharsRead = sbuf->sgetn(buf.get(), bufSize);
|
||||
// The conversion to std::size_t is safe because sbuf->sgetn() returned a
|
||||
// non-negative value which, in this case, can't exceed bufSize.
|
||||
result.append(buf.get(), streamsizeToSize_t((nbCharsRead)));
|
||||
} while (nbCharsRead == bufSize);
|
||||
|
||||
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
|
||||
}
|
||||
|
||||
// Auxiliary function for test_RawEmbeddedResource()
|
||||
void auxTest_RawEmbeddedResource_istream()
|
||||
{
|
||||
cout << "Testing EmbeddedResourceManager::getIStream()" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
unique_ptr<std::istream> iStream(resMgr->getIStream("/path/to/resource1"));
|
||||
// This is convenient, but be aware that still in 2017, some buggy C++
|
||||
// compilers don't allow the exception to be caught: cf.
|
||||
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145>.
|
||||
iStream->exceptions(std::ios_base::badbit);
|
||||
// Just to show an efficient algorithm. For real applications, use larger
|
||||
// buffer sizes!
|
||||
static constexpr std::size_t bufSize = 4;
|
||||
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
|
||||
string result;
|
||||
|
||||
do {
|
||||
iStream->read(buf.get(), bufSize);
|
||||
result.append(buf.get(), iStream->gcount());
|
||||
} while (*iStream); // iStream *points* to an std::istream
|
||||
|
||||
// 1) If set, badbit would have caused an exception to be raised (see above).
|
||||
// 2) failbit doesn't necessarily indicate an error here: it is set as soon
|
||||
// as the read() call can't provide the requested number of characters.
|
||||
SG_VERIFY(iStream->eof() && !iStream->bad());
|
||||
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
|
||||
}
|
||||
|
||||
void test_RawEmbeddedResource()
|
||||
{
|
||||
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
|
||||
"a RawEmbeddedResource" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
string("This is a simple embedded resource test."));
|
||||
|
||||
// Get a shared_ptr to a const AbstractEmbeddedResource
|
||||
const auto res1abs = resMgr->getResource("/path/to/resource1");
|
||||
// Okay because we know this resource is not a compressed one
|
||||
const auto res1 =
|
||||
std::dynamic_pointer_cast<const RawEmbeddedResource>(res1abs);
|
||||
SG_VERIFY(res1);
|
||||
|
||||
// Print a representation of the resource metadata
|
||||
std::cout << "\n/path/to/resource1 -> " << *res1 << "\n\n";
|
||||
|
||||
// The following methods would work the same with res1abs
|
||||
SG_CHECK_EQUAL_NOSTREAM(res1->compressionType(),
|
||||
AbstractEmbeddedResource::CompressionType::NONE);
|
||||
SG_CHECK_EQUAL(res1->compressionDescr(), "none");
|
||||
|
||||
SG_CHECK_EQUAL(res1->rawPtr(), res1Array);
|
||||
SG_CHECK_EQUAL(res1->rawSize(), sizeof(res1Array) - 1); // see above
|
||||
SG_CHECK_EQUAL(res1->str(),
|
||||
string("This is a simple embedded resource test."));
|
||||
|
||||
auxTest_RawEmbeddedResource_streambuf();
|
||||
auxTest_RawEmbeddedResource_istream();
|
||||
|
||||
// Just reload and recheck the resource, because we can :)
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
string("This is a simple embedded resource test."));
|
||||
}
|
||||
|
||||
void test_ZlibEmbeddedResource()
|
||||
{
|
||||
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
|
||||
"a ZlibEmbeddedResource" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
|
||||
lipsum);
|
||||
|
||||
// Get a shared_ptr to a const AbstractEmbeddedResource
|
||||
const auto res2abs = resMgr->getResource("/path/to/resource2");
|
||||
// Okay because we know this resource is a Zlib-compressed one
|
||||
const auto res2 =
|
||||
std::dynamic_pointer_cast<const ZlibEmbeddedResource>(res2abs);
|
||||
SG_VERIFY(res2);
|
||||
|
||||
SG_CHECK_EQUAL(res2->uncompressedSize(), lipsum.size());
|
||||
|
||||
// Print a representation of the resource metadata
|
||||
std::cout << "\n/path/to/resource2 -> " << *res2 << "\n\n";
|
||||
cout << "Resource 2 compression ratio: " <<
|
||||
static_cast<float>(res2->uncompressedSize()) /
|
||||
static_cast<float>(res2->rawSize()) << "\n";
|
||||
|
||||
// Just reload and recheck the resource
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"), lipsum);
|
||||
}
|
||||
|
||||
void test_getMissingResources()
|
||||
{
|
||||
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
|
||||
"fetch inexistent resources" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
SG_VERIFY(!resMgr->getResourceOrNullPtr("/inexistant/resource"));
|
||||
|
||||
bool gotException = false;
|
||||
try {
|
||||
resMgr->getResource("/inexistant/resource");
|
||||
} catch (const sg_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
|
||||
gotException = false;
|
||||
try {
|
||||
resMgr->getString("/other/inexistant/resource");
|
||||
} catch (const sg_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
void test_addAlreadyExistingResource()
|
||||
{
|
||||
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
|
||||
"add an already existing resource" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
for (const string& locale: {"", "fr", "fr_FR"}) {
|
||||
// For these tests, we don't care about the resource contents -> no need
|
||||
// to substract 1 from the result of sizeof() as we did above.
|
||||
unique_ptr<const RawEmbeddedResource> someRes(
|
||||
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray)));
|
||||
|
||||
bool gotException = false;
|
||||
try {
|
||||
resMgr->addResource("/path/to/resource1", std::move(someRes), locale);
|
||||
} catch (const sg_error&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
}
|
||||
|
||||
void test_localeDependencyOfResourceFetching()
|
||||
{
|
||||
cout << "Testing the locale-dependency of resource fetching from "
|
||||
"EmbeddedResourceManager" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
resMgr->selectLocale(""); // select the default locale
|
||||
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"This is a simple embedded resource test.");
|
||||
|
||||
// Switch to the 'fr_FR' locale (French from France)
|
||||
resMgr->selectLocale("fr_FR");
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"Ceci est un petit test de ressource embarquée (variante "
|
||||
"fr_FR).");
|
||||
|
||||
// This one is for the 'fr' “locale”, obtained as fallback since there is no
|
||||
// resource mapped to /path/to/resource2 for the 'fr_FR' “locale”.
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
|
||||
"Un lorem ipsum un peu plus court...");
|
||||
|
||||
// Explicitly ask for the resource in the default locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
|
||||
"This is a simple embedded resource test.");
|
||||
|
||||
// Switch to the 'fr' locale (French)
|
||||
resMgr->selectLocale("fr");
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"Ceci est un petit test de ressource embarquée.");
|
||||
|
||||
// Explicitly ask for the resource in the 'fr_FR' locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
|
||||
"Ceci est un petit test de ressource embarquée "
|
||||
"(variante fr_FR).");
|
||||
|
||||
// Switch to the default locale
|
||||
resMgr->selectLocale("");
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"This is a simple embedded resource test.");
|
||||
|
||||
// Explicitly ask for the resource in the 'fr' locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr"),
|
||||
"Ceci est un petit test de ressource embarquée.");
|
||||
|
||||
// Explicitly ask for the resource in the 'fr_FR' locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
|
||||
"Ceci est un petit test de ressource embarquée "
|
||||
"(variante fr_FR).");
|
||||
|
||||
// Explicitly ask for the resource in the default locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
|
||||
"This is a simple embedded resource test.");
|
||||
}
|
||||
|
||||
void test_getLocaleAndSelectLocale()
|
||||
{
|
||||
cout << "Testing the getLocale() and selectLocale() methods of "
|
||||
"EmbeddedResourceManager" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
for (const string& locale: {"", "fr", "fr_FR", "de_DE"}) {
|
||||
// The important effects of setLocale() are tested in
|
||||
// test_localeDependencyOfResourceFetching()
|
||||
resMgr->selectLocale(locale);
|
||||
SG_CHECK_EQUAL(resMgr->getLocale(), locale);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Initialize the EmbeddedResourceManager instance, add a few resources
|
||||
// to it and call its selectLocale() method.
|
||||
initResources();
|
||||
|
||||
test_RawEmbeddedResource();
|
||||
test_ZlibEmbeddedResource();
|
||||
test_getMissingResources();
|
||||
test_addAlreadyExistingResource();
|
||||
test_localeDependencyOfResourceFetching();
|
||||
test_getLocaleAndSelectLocale();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -10,13 +10,14 @@ if(ENABLE_TESTS)
|
||||
add_executable(test_metar test_metar.cxx)
|
||||
|
||||
if (SIMGEAR_SHARED)
|
||||
target_link_libraries(test_metar SimGearScene)
|
||||
target_link_libraries(test_metar SimGearScene ${GDAL_LIBRARY})
|
||||
else()
|
||||
target_link_libraries(test_metar
|
||||
SimGearScene SimGearCore
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${ZLIB_LIBRARY}
|
||||
${RT_LIBRARY})
|
||||
${RT_LIBRARY}
|
||||
${GDAL_LIBRARY})
|
||||
endif()
|
||||
|
||||
add_test(metar ${EXECUTABLE_OUTPUT_PATH}/test_metar)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* $Id$
|
||||
**************************************************************************/
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* $Id$
|
||||
**************************************************************************/
|
||||
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* $Id$
|
||||
**************************************************************************/
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <cmath>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include "DNSClient.hxx"
|
||||
#include <udns.h>
|
||||
#include <time.h>
|
||||
@@ -190,11 +191,11 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
|
||||
r->ttl = result->dnsnaptr_ttl;
|
||||
for (int i = 0; i < result->dnsnaptr_nrr; i++) {
|
||||
if( !r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service )
|
||||
return;
|
||||
continue;
|
||||
|
||||
//TODO: case ignore and result flags may have more than one flag
|
||||
if( !r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags )
|
||||
return;
|
||||
continue;
|
||||
|
||||
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
|
||||
r->entries.push_back(naptr);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPFileRequest.hxx"
|
||||
@@ -168,8 +169,32 @@ void Client::update(int waitTimeout)
|
||||
return;
|
||||
}
|
||||
|
||||
int remainingActive, messagesInQueue, numFds;
|
||||
int remainingActive, messagesInQueue;
|
||||
#if defined(SG_MAC)
|
||||
// Mac 10.8 libCurl lacks this, let's keep compat for now
|
||||
fd_set curlReadFDs, curlWriteFDs, curlErrorFDs;
|
||||
int maxFD;
|
||||
curl_multi_fdset(d->curlMulti,
|
||||
&curlReadFDs,
|
||||
&curlWriteFDs,
|
||||
&curlErrorFDs,
|
||||
&maxFD);
|
||||
|
||||
struct timeval timeout;
|
||||
long t;
|
||||
|
||||
curl_multi_timeout(d->curlMulti, &t);
|
||||
if ((t < 0) || (t > waitTimeout)) {
|
||||
t = waitTimeout;
|
||||
}
|
||||
|
||||
timeout.tv_sec = t / 1000;
|
||||
timeout.tv_usec = (t % 1000) * 1000;
|
||||
::select(maxFD, &curlReadFDs, &curlWriteFDs, &curlErrorFDs, &timeout);
|
||||
#else
|
||||
int numFds;
|
||||
curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
|
||||
#endif
|
||||
curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
|
||||
CURLMsg* msg;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "HTTPFileRequest.hxx"
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "HTTPMemoryRequest.hxx"
|
||||
|
||||
namespace simgear
|
||||
|
||||
@@ -16,10 +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.
|
||||
|
||||
#include "HTTPRepository.hxx"
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "HTTPRepository.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// 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 <simgear_config.h>
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@@ -4,12 +4,16 @@ set(HEADERS
|
||||
sgstream.hxx
|
||||
gzfstream.hxx
|
||||
gzcontainerfile.hxx
|
||||
CharArrayStream.hxx
|
||||
zlibstream.hxx
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
sgstream.cxx
|
||||
gzfstream.cxx
|
||||
gzcontainerfile.cxx
|
||||
CharArrayStream.cxx
|
||||
zlibstream.cxx
|
||||
)
|
||||
|
||||
simgear_component(IOStreams io/iostreams "${SOURCES}" "${HEADERS}")
|
||||
@@ -20,4 +24,12 @@ if(ENABLE_TESTS)
|
||||
target_link_libraries(test_streams ${TEST_LIBS})
|
||||
add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams)
|
||||
|
||||
add_executable(test_CharArrayStream CharArrayStream_test.cxx)
|
||||
target_link_libraries(test_CharArrayStream ${TEST_LIBS})
|
||||
add_test(CharArrayStream ${EXECUTABLE_OUTPUT_PATH}/test_CharArrayStream)
|
||||
|
||||
add_executable(test_zlibstream zlibstream_test.cxx)
|
||||
target_link_libraries(test_zlibstream ${TEST_LIBS})
|
||||
add_test(zlibstream ${EXECUTABLE_OUTPUT_PATH}/test_zlibstream)
|
||||
|
||||
endif(ENABLE_TESTS)
|
||||
|
||||
291
simgear/io/iostreams/CharArrayStream.cxx
Normal file
291
simgear/io/iostreams/CharArrayStream.cxx
Normal file
@@ -0,0 +1,291 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// CharArrayStream.cxx --- IOStreams classes for reading from, and writing to
|
||||
// char arrays
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <ios> // std::streamsize
|
||||
#include <istream> // std::istream and std::iostream
|
||||
#include <ostream> // std::ostream
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
#include <cassert>
|
||||
|
||||
#include "CharArrayStream.hxx"
|
||||
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayStreambuf class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayStreambuf::CharArrayStreambuf(char* buf, std::size_t bufSize)
|
||||
: _buf(buf),
|
||||
_bufSize(bufSize)
|
||||
{
|
||||
setg(_buf, _buf, _buf + _bufSize);
|
||||
setp(_buf, _buf + _bufSize);
|
||||
}
|
||||
|
||||
char* CharArrayStreambuf::data() const
|
||||
{
|
||||
return _buf;
|
||||
}
|
||||
|
||||
std::size_t CharArrayStreambuf::size() const
|
||||
{
|
||||
return _bufSize;
|
||||
}
|
||||
|
||||
int CharArrayStreambuf::underflow()
|
||||
{
|
||||
return (gptr() == egptr()) ? traits::eof() : traits::to_int_type(*gptr());
|
||||
}
|
||||
|
||||
int CharArrayStreambuf::overflow(int c)
|
||||
{
|
||||
// cf. §27.7.1, footnote 309 of the C++11 standard
|
||||
if (traits::eq_int_type(c, traits::eof())) {
|
||||
return traits::not_eof(c);
|
||||
} else {
|
||||
// This class never writes beyond the end of the array (_buf + _bufSize)
|
||||
return traits::eof();
|
||||
}
|
||||
}
|
||||
|
||||
std::streamsize CharArrayStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
{
|
||||
assert(n >= 0);
|
||||
std::ptrdiff_t avail = egptr() - gptr();
|
||||
// Compute min(avail, n). The cast is safe, because in its branch, one has
|
||||
// 0 <= n < avail, which is of type std::ptrdiff_t.
|
||||
std::ptrdiff_t nbChars = ( (n >= avail) ?
|
||||
avail : static_cast<std::ptrdiff_t>(n) );
|
||||
std::copy(gptr(), gptr() + nbChars, dest);
|
||||
// eback() == _buf and egptr() == _buf + _bufSize
|
||||
// I don't use gbump(), because it takes an int...
|
||||
setg(eback(), gptr() + nbChars, egptr());
|
||||
|
||||
// Cast safe because 0 <= nbChars <= n, which is of type std::streamsize
|
||||
return static_cast<std::streamsize>(nbChars); // number of chars copied
|
||||
}
|
||||
|
||||
std::streamsize CharArrayStreambuf::xsputn(const char* s, std::streamsize n)
|
||||
{
|
||||
assert(n >= 0);
|
||||
std::ptrdiff_t availSpace = epptr() - pptr();
|
||||
// Compute min(availSpace, n). The cast is safe, because in its branch, one
|
||||
// has 0 <= n < availSpace, which is of type std::ptrdiff_t.
|
||||
std::ptrdiff_t nbChars = ( (n >= availSpace) ?
|
||||
availSpace : static_cast<std::ptrdiff_t>(n) );
|
||||
std::copy(s, s + nbChars, pptr());
|
||||
// epptr() == _buf + _bufSize
|
||||
// I don't use pbump(), because it takes an int...
|
||||
setp(pptr() + nbChars, epptr());
|
||||
|
||||
// Cast safe because 0 <= nbChars <= n, which is of type std::streamsize
|
||||
return static_cast<std::streamsize>(nbChars); // number of chars copied
|
||||
}
|
||||
|
||||
std::streamsize CharArrayStreambuf::showmanyc()
|
||||
{
|
||||
// It is certain that underflow() will return EOF if gptr() == egptr().
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::streampos CharArrayStreambuf::seekoff(std::streamoff off,
|
||||
std::ios_base::seekdir way,
|
||||
std::ios_base::openmode which)
|
||||
{
|
||||
bool positionInputSeq = false;
|
||||
bool positionOutputSeq = false;
|
||||
char* ptr = nullptr;
|
||||
|
||||
// cf. §27.8.2.4 of the C++11 standard
|
||||
if ((which & std::ios_base::in) == std::ios_base::in) {
|
||||
positionInputSeq = true;
|
||||
ptr = gptr();
|
||||
}
|
||||
|
||||
if ((which & std::ios_base::out) == std::ios_base::out) {
|
||||
positionOutputSeq = true;
|
||||
ptr = pptr();
|
||||
}
|
||||
|
||||
if ((!positionInputSeq && !positionOutputSeq) ||
|
||||
(positionInputSeq && positionOutputSeq &&
|
||||
way != std::ios_base::beg && way != std::ios_base::end)) {
|
||||
return std::streampos(std::streamoff(-1));
|
||||
}
|
||||
|
||||
// If we reached this point and (positionInputSeq && positionOutputSeq),
|
||||
// then (way == std::ios_base::beg || way == std::ios_base::end) and
|
||||
// therefore 'ptr' won't be used.
|
||||
std::streamoff refOffset;
|
||||
static_assert(sizeof(std::streamoff) >= sizeof(std::ptrdiff_t),
|
||||
"Unexpected: sizeof(std::streamoff) < sizeof(std::ptrdiff_t)");
|
||||
static_assert(sizeof(std::streamoff) >= sizeof(std::size_t),
|
||||
"Unexpected: sizeof(std::streamoff) < sizeof(std::size_t)");
|
||||
|
||||
if (way == std::ios_base::beg) {
|
||||
refOffset = 0;
|
||||
} else if (way == std::ios_base::cur) {
|
||||
refOffset = static_cast<std::streamoff>(ptr - _buf);
|
||||
} else {
|
||||
assert(way == std::ios_base::end);
|
||||
refOffset = static_cast<std::streamoff>(_bufSize);
|
||||
}
|
||||
|
||||
// Offset, relatively to _buf, where we are supposed to seek
|
||||
std::streamoff totalOffset = refOffset + off;
|
||||
typedef typename std::make_unsigned<std::streamoff>::type uStreamOff;
|
||||
|
||||
if (totalOffset < 0 || static_cast<uStreamOff>(totalOffset) > _bufSize) {
|
||||
return std::streampos(std::streamoff(-1));
|
||||
} else {
|
||||
// Safe because 0 <= totalOffset <= _bufSize, which is an std::size_t
|
||||
char* newPtr = _buf + static_cast<std::size_t>(totalOffset);
|
||||
|
||||
if (positionInputSeq) {
|
||||
// eback() == _buf and egptr() == _buf + _bufSize
|
||||
setg(eback(), newPtr, egptr());
|
||||
}
|
||||
|
||||
if (positionOutputSeq) {
|
||||
// epptr() == _buf + _bufSize
|
||||
setp(newPtr, epptr());
|
||||
}
|
||||
|
||||
// C++11's §27.8.2.4 item 12 (for stringbuf) would return refOffset. This
|
||||
// makes no sense IMHO, in particular when 'way' is std::ios_base::beg or
|
||||
// std::ios_base::end. Return the new offset (from the beginning of
|
||||
// '_buf') instead. Note that this doesn't violate anything, because
|
||||
// §27.6.3.4.2 grants full freedom as to the semantics of seekoff() to
|
||||
// classes derived from basic_streambuf.
|
||||
//
|
||||
// My interpretation is consistent with items 13 and 14 of §27.8.2.4
|
||||
// concerning seekpos(), whereas item 12 is not (if item 12 were followed
|
||||
// to the letter, seekoff() would always return 0 on success when
|
||||
// way == std::ios_base::beg, and therefore items 13 and 14 would be
|
||||
// incompatible).
|
||||
return std::streampos(totalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
std::streampos CharArrayStreambuf::seekpos(std::streampos pos,
|
||||
std::ios_base::openmode which)
|
||||
{
|
||||
return seekoff(std::streamoff(pos), std::ios_base::beg, which);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * ROCharArrayStreambuf class *
|
||||
// ***************************************************************************
|
||||
ROCharArrayStreambuf::ROCharArrayStreambuf(const char* buf, std::size_t bufSize)
|
||||
: CharArrayStreambuf(const_cast<char*>(buf), bufSize)
|
||||
{ }
|
||||
|
||||
const char* ROCharArrayStreambuf::data() const
|
||||
{
|
||||
return const_cast<const char*>(CharArrayStreambuf::data());
|
||||
}
|
||||
|
||||
int ROCharArrayStreambuf::overflow(int c)
|
||||
{
|
||||
return traits::eof(); // indicate failure
|
||||
}
|
||||
|
||||
std::streamsize ROCharArrayStreambuf::xsputn(const char* s, std::streamsize n)
|
||||
{
|
||||
return 0; // number of chars written
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayIStream class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayIStream::CharArrayIStream(const char* buf, std::size_t bufSize)
|
||||
: std::istream(nullptr),
|
||||
_streamBuf(buf, bufSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
const char* CharArrayIStream::data() const
|
||||
{
|
||||
return _streamBuf.data();
|
||||
}
|
||||
|
||||
std::size_t CharArrayIStream::size() const
|
||||
{
|
||||
return _streamBuf.size();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayOStream class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayOStream::CharArrayOStream(char* buf, std::size_t bufSize)
|
||||
: std::ostream(nullptr),
|
||||
_streamBuf(buf, bufSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
char* CharArrayOStream::data() const
|
||||
{
|
||||
return _streamBuf.data();
|
||||
}
|
||||
|
||||
std::size_t CharArrayOStream::size() const
|
||||
{
|
||||
return _streamBuf.size();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayIOStream class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayIOStream::CharArrayIOStream(char* buf, std::size_t bufSize)
|
||||
: std::iostream(nullptr),
|
||||
_streamBuf(buf, bufSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
char* CharArrayIOStream::data() const
|
||||
{
|
||||
return _streamBuf.data();
|
||||
}
|
||||
|
||||
std::size_t CharArrayIOStream::size() const
|
||||
{
|
||||
return _streamBuf.size();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
169
simgear/io/iostreams/CharArrayStream.hxx
Normal file
169
simgear/io/iostreams/CharArrayStream.hxx
Normal file
@@ -0,0 +1,169 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// CharArrayStream.hxx --- IOStreams classes for reading from, and writing to
|
||||
// char arrays
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef SG_CHAR_ARRAY_STREAM_HXX
|
||||
#define SG_CHAR_ARRAY_STREAM_HXX
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <streambuf>
|
||||
#include <ios> // std::streamsize, std::streampos...
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
// Contrary to std::stringstream and its (i/o)stringstream friends, the
|
||||
// classes in this file allow one to work on an array of char (that could be
|
||||
// for instance static data) without having to make a whole copy of it.
|
||||
//
|
||||
// There are five classes defined here in the 'simgear' namespace:
|
||||
// - CharArrayStreambuf subclass of std::streambuf stream buffer
|
||||
// - ROCharArrayStreambuf subclass of CharArrayStreambuf stream buffer
|
||||
// - CharArrayIStream subclass of std::istream input stream
|
||||
// - CharArrayOStream subclass of std::ostream output stream
|
||||
// - CharArrayIOStream subclass of std::iostream input/output stream
|
||||
//
|
||||
// The main class is CharArrayStreambuf. ROCharArrayStreambuf is a read-only
|
||||
// subclass of CharArrayStreambuf. The other three are very simple convenience
|
||||
// classes, using either CharArrayStreambuf or ROCharArrayStreambuf as their
|
||||
// stream buffer class. One can easily work with CharArrayStreambuf or
|
||||
// ROCharArrayStreambuf only, either directly or after attaching an instance
|
||||
// to an std::istream, std::ostream or std::iostream instance (using for
|
||||
// example constructors like std::istream(std::streambuf* sb) or the rdbuf()
|
||||
// method of stream classes).
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// Input/output stream buffer class that reads from, and writes to a fixed
|
||||
// buffer in memory specified in the constructor. This buffer must remain
|
||||
// alive as long as the stream buffer object is used (the CharArrayStreambuf
|
||||
// class works directly on that buffer without making any copy of it).
|
||||
//
|
||||
// Because reads and writes are directly performed on the buffer specified in
|
||||
// the constructor, this stream buffer class has no caching behavior. You may
|
||||
// use pubsync() if you like, but that is completely useless by design (it
|
||||
// uses the default implementation in std::streambuf, which does nothing).
|
||||
//
|
||||
// CharArrayStreambuf may share similarities in features with
|
||||
// std::strstreambuf (deprecated since C++98). However, at least one big
|
||||
// difference is that CharArrayStreambuf does no dynamic memory allocation
|
||||
// whatsoever. It works on a fixed-size-fixed-location buffer passed in the
|
||||
// constructor, and nothing more. It does prevent overflowing the buffer,
|
||||
// since it knows perfectly well where the buffer starts and ends.
|
||||
class CharArrayStreambuf: public std::streambuf
|
||||
{
|
||||
public:
|
||||
explicit CharArrayStreambuf(char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the buffer start pointer and size (same method names as for
|
||||
// std::string)
|
||||
char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
protected:
|
||||
virtual int underflow() override;
|
||||
virtual int overflow(int c = std::char_traits<char>::eof()) override;
|
||||
// Optional override when subclassing std::streambuf. This is the most
|
||||
// efficient way of reading several characters.
|
||||
virtual std::streamsize xsgetn(char* dest, std::streamsize n) override;
|
||||
// Ditto for writing
|
||||
virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
|
||||
virtual std::streamsize showmanyc() override;
|
||||
virtual std::streampos seekoff(
|
||||
std::streamoff off,
|
||||
std::ios_base::seekdir way,
|
||||
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
|
||||
override;
|
||||
virtual std::streampos seekpos(
|
||||
std::streampos pos,
|
||||
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
|
||||
override;
|
||||
|
||||
private:
|
||||
// These two define the buffer managed by the CharArrayStreambuf instance.
|
||||
char* const _buf;
|
||||
const std::size_t _bufSize;
|
||||
};
|
||||
|
||||
// Read-only version of CharArrayStreambuf
|
||||
class ROCharArrayStreambuf: public CharArrayStreambuf
|
||||
{
|
||||
public:
|
||||
explicit ROCharArrayStreambuf(const char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessor for the buffer start pointer (same method name as for
|
||||
// std::string)
|
||||
const char* data() const;
|
||||
|
||||
private:
|
||||
// Override methods pertaining to write access
|
||||
virtual int overflow(int c = std::char_traits<char>::eof()) override;
|
||||
virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
|
||||
};
|
||||
|
||||
// Convenience class: std::istream subclass based on ROCharArrayStreambuf
|
||||
class CharArrayIStream: public std::istream
|
||||
{
|
||||
public:
|
||||
// Same parameters as for ROCharArrayStreambuf
|
||||
explicit CharArrayIStream(const char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the underlying buffer start pointer and size
|
||||
const char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
ROCharArrayStreambuf _streamBuf;
|
||||
};
|
||||
|
||||
// Convenience class: std::ostream subclass based on CharArrayStreambuf
|
||||
class CharArrayOStream: public std::ostream
|
||||
{
|
||||
public:
|
||||
// Same parameters as for CharArrayStreambuf
|
||||
explicit CharArrayOStream(char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the underlying buffer start pointer and size
|
||||
char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
CharArrayStreambuf _streamBuf;
|
||||
};
|
||||
|
||||
// Convenience class: std::iostream subclass based on CharArrayStreambuf
|
||||
class CharArrayIOStream: public std::iostream
|
||||
{
|
||||
public:
|
||||
// Same parameters as for CharArrayStreambuf
|
||||
explicit CharArrayIOStream(char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the underlying buffer start pointer and size
|
||||
char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
CharArrayStreambuf _streamBuf;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_CHAR_ARRAY_STREAM_HXX
|
||||
439
simgear/io/iostreams/CharArrayStream_test.cxx
Normal file
439
simgear/io/iostreams/CharArrayStream_test.cxx
Normal file
@@ -0,0 +1,439 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// CharArrayStream_test.cxx --- Automated tests for CharArrayStream.cxx
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <ios> // std::basic_ios, std::streamsize...
|
||||
#include <iostream> // std::ios_base, std::cerr, etc.
|
||||
#include <sstream>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <cassert>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstring> // std::strlen()
|
||||
#include <algorithm> // std::fill_n()
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include "CharArrayStream.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
|
||||
|
||||
|
||||
// Safely convert a non-negative std::streamsize into an std::size_t. If
|
||||
// impossible, bail out.
|
||||
static std::size_t streamsizeToSize_t(std::streamsize n)
|
||||
{
|
||||
SG_CHECK_GE(n, 0);
|
||||
SG_CHECK_LE(static_cast<uStreamSize>(n),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
|
||||
return static_cast<std::size_t>(n);
|
||||
}
|
||||
|
||||
void test_CharArrayStreambuf_basicOperations()
|
||||
{
|
||||
cerr << "Testing basic operations on CharArrayStreambuf\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
std::istringstream text_ss(text);
|
||||
string canary = "YoY";
|
||||
// Reserve space for our little canary
|
||||
const std::size_t bufSize = text.size() + canary.size();
|
||||
std::unique_ptr<char[]> buf(new char[bufSize]);
|
||||
int ch;
|
||||
std::streamsize n;
|
||||
std::streampos pos;
|
||||
|
||||
// Only allow arraySBuf to read from, and write to the first text.size()
|
||||
// chars of 'buf'
|
||||
simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size());
|
||||
|
||||
// 1) Write a copy of the 'text' string at buf.get(), testing various write
|
||||
// and seek methods.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Write "01" with two sputc() calls
|
||||
ch = arraySBuf.sputc(text[0]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0' && buf[0] == '0');
|
||||
ch = arraySBuf.sputc(text[1]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1' && buf[1] == '1');
|
||||
// Write the 34 following chars of 'text' with one sputn() call
|
||||
n = arraySBuf.sputn(&text[2], 34);
|
||||
SG_CHECK_EQUAL(n, 34);
|
||||
SG_CHECK_EQUAL(string(&buf[2], 34), "23456789abcdefghijklmnopqrstuvwxyz");
|
||||
|
||||
// Indirect test of seekpos(): position the write stream pointer a bit further
|
||||
pos = arraySBuf.pubseekpos(43, std::ios_base::out);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(43));
|
||||
// Write 7 more chars with sputn()
|
||||
n = arraySBuf.sputn(&text[43], 7);
|
||||
SG_CHECK_EQUAL(n, 7);
|
||||
SG_CHECK_EQUAL(string(&buf[43], 7), "\nGHIJK ");
|
||||
// Indirect test of seekoff(): seek backwards relatively to the current write
|
||||
// pointer position
|
||||
pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("\nABCDEF\nGHIJK ")),
|
||||
std::ios_base::cur, std::ios_base::out);
|
||||
// 10 + 26, i.e., after the lowercase alphabet
|
||||
SG_CHECK_EQUAL(pos, std::streampos(36));
|
||||
// Write "\nABCD" to buf in one sputn() call
|
||||
n = arraySBuf.sputn(&text[36], 5);
|
||||
// Now write "EF" in two sputc() calls
|
||||
ch = arraySBuf.sputc(text[41]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'E' && buf[41] == 'E');
|
||||
ch = arraySBuf.sputc(text[42]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'F' && buf[42] == 'F');
|
||||
|
||||
// Place a canary to check that arraySBuf doesn't write beyond the end of buf
|
||||
std::copy(canary.begin(), canary.end(), &buf[text.size()]);
|
||||
|
||||
// Check seeking from arraySBuf's end (which is at offset text.size(), *not*
|
||||
// bufSize: cf. the construction of arraySBuf!).
|
||||
pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("LMNOPQ")),
|
||||
std::ios_base::end, std::ios_base::out);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(text.size() - std::strlen("LMNOPQ")));
|
||||
// Write "LMNOPQ" to buf in one sputn() call. The other characters won't be
|
||||
// written, because they would go past the end of the buffer managed by
|
||||
// 'arraySBuf' (i.e., the first text.size() chars of 'buf').
|
||||
static const char someChars[] = "LMNOPQ+buffer overrun that will be blocked";
|
||||
n = arraySBuf.sputn(someChars, sizeof(someChars));
|
||||
|
||||
// Check the number of chars actually written
|
||||
SG_CHECK_EQUAL(n, std::strlen("LMNOPQ"));
|
||||
// Check that our canary starting at buf[text.size()] is still there and
|
||||
// intact
|
||||
SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary);
|
||||
// Check that we now have an exact copy of 'text' in the managed buffer
|
||||
SG_CHECK_EQUAL(string(&buf[0], text.size()), text);
|
||||
|
||||
// 2) Read back the copy of 'text' in 'buf', using various read and seek
|
||||
// methods.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
ch = arraySBuf.sgetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1');
|
||||
ch = arraySBuf.snextc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
|
||||
ch = arraySBuf.sputbackc('4');
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
|
||||
ch = arraySBuf.sputbackc('u'); // doesn't match what we read from the stream
|
||||
SG_VERIFY(ch == EOF);
|
||||
ch = arraySBuf.sputbackc('3'); // this one does
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
|
||||
|
||||
static constexpr std::streamsize buf2Size = 10;
|
||||
char buf2[buf2Size];
|
||||
// Most efficient way (with the underlying xsgetn()) to read several chars
|
||||
// at once.
|
||||
n = arraySBuf.sgetn(buf2, buf2Size);
|
||||
SG_CHECK_EQUAL(n, buf2Size);
|
||||
SG_CHECK_EQUAL(string(buf2, static_cast<std::size_t>(buf2Size)),
|
||||
"3456789abc");
|
||||
|
||||
ch = arraySBuf.sungetc(); // same as sputbackc(), except no value to check
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'c');
|
||||
ch = arraySBuf.sungetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
ch = arraySBuf.sputbackc('b'); // this one does
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
|
||||
n = arraySBuf.sgetn(buf2, buf2Size);
|
||||
SG_CHECK_EQUAL(n, buf2Size);
|
||||
SG_CHECK_EQUAL(string(buf2, static_cast<std::size_t>(buf2Size)),
|
||||
"bcdefghijk");
|
||||
|
||||
ch = arraySBuf.sungetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'k');
|
||||
|
||||
static char buf3[64];
|
||||
n = arraySBuf.sgetn(buf3, sizeof(buf3));
|
||||
SG_CHECK_EQUAL(n, 36);
|
||||
SG_CHECK_EQUAL(string(buf3, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ");
|
||||
|
||||
SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF);
|
||||
|
||||
// Check we can independently set the read and write pointers for arraySBuf
|
||||
pos = arraySBuf.pubseekpos(10, std::ios_base::in);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(10));
|
||||
pos = arraySBuf.pubseekpos(13, std::ios_base::out);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(13));
|
||||
|
||||
// Write "DEF" where there is currently "def" in 'buf'.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char c = 'D' + i;
|
||||
ch = arraySBuf.sputc(c);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == c && buf[i+13] == c);
|
||||
}
|
||||
|
||||
n = arraySBuf.sgetn(buf3, 6);
|
||||
SG_CHECK_EQUAL(n, 6);
|
||||
SG_CHECK_EQUAL(string(buf3, 6), "abcDEF");
|
||||
|
||||
// Set both stream pointers at once (read and write)
|
||||
pos = arraySBuf.pubseekpos(10, std::ios_base::in | std::ios_base::out);
|
||||
SG_VERIFY(pos == std::streampos(10));
|
||||
|
||||
// Write "ABC" to buf in one sputn() call
|
||||
n = arraySBuf.sputn("ABC", 3);
|
||||
SG_CHECK_EQUAL(n, 3);
|
||||
SG_CHECK_EQUAL(string(&buf[10], 3), "ABC");
|
||||
|
||||
// Indirect test of seekoff(): seek backwards relatively to the current read
|
||||
// pointer position
|
||||
pos = arraySBuf.pubseekoff(-3, std::ios_base::cur, std::ios_base::in);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(7));
|
||||
|
||||
n = arraySBuf.sgetn(buf3, 12);
|
||||
SG_CHECK_EQUAL(n, 12);
|
||||
SG_CHECK_EQUAL(string(buf3, 12), "789ABCDEFghi");
|
||||
}
|
||||
|
||||
void test_CharArrayStreambuf_readOrWriteLargestPossibleAmount()
|
||||
{
|
||||
cerr << "Testing reading and writing from/to CharArrayStreambuf with the "
|
||||
"largest possible value passed as the 'n' argument for sgetn()/sputn() "
|
||||
"(number of chars to read or write)\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
string canary = "ZaZ";
|
||||
// Reserve space for our little canary
|
||||
const std::size_t bufSize = text.size() + canary.size();
|
||||
std::unique_ptr<char[]> buf(new char[bufSize]);
|
||||
std::streamsize n_s;
|
||||
std::size_t n;
|
||||
std::streampos pos;
|
||||
|
||||
// Place a canary to check that arraySBuf doesn't write beyond the end of buf
|
||||
std::copy(canary.begin(), canary.end(), &buf[text.size()]);
|
||||
|
||||
// Only allow arraySBuf to read from, and write to the first text.size()
|
||||
// chars of 'buf'
|
||||
simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size());
|
||||
|
||||
n_s = arraySBuf.sputn(text.c_str(),
|
||||
std::numeric_limits<std::streamsize>::max());
|
||||
// The conversion to std::size_t is safe because arraySBuf.sputn() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by 'arraySBuf', i.e. text.size().
|
||||
n = streamsizeToSize_t(n_s);
|
||||
SG_CHECK_EQUAL(n, arraySBuf.size());
|
||||
SG_CHECK_EQUAL(n, text.size());
|
||||
SG_CHECK_EQUAL(string(&buf[0], n), text);
|
||||
|
||||
// Check that our canary starting at &buf[text.size()] is still there and
|
||||
// intact
|
||||
SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary);
|
||||
|
||||
// The “get” stream pointer is still at the beginning of the buffer managed
|
||||
// by 'arraySBuf'. Let's ask for the maximum amount of chars from it to be
|
||||
// written to a new buffer, 'buf2'.
|
||||
std::unique_ptr<char[]> buf2(new char[text.size()]);
|
||||
n_s = arraySBuf.sgetn(&buf2[0],
|
||||
std::numeric_limits<std::streamsize>::max());
|
||||
// The conversion to std::size_t is safe because arraySBuf.sgetn() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by 'arraySBuf', i.e. text.size().
|
||||
n = streamsizeToSize_t(n_s);
|
||||
SG_CHECK_EQUAL(n, arraySBuf.size());
|
||||
SG_CHECK_EQUAL(string(&buf2[0], n), text);
|
||||
|
||||
SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF);
|
||||
}
|
||||
|
||||
void test_CharArrayIStream_simple()
|
||||
{
|
||||
// This also tests ROCharArrayStreambuf, since it is used as
|
||||
// CharArrayIStream's stream buffer class.
|
||||
cerr << "Testing read operations from CharArrayIStream\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
std::unique_ptr<char[]> buf(new char[text.size()]);
|
||||
std::size_t n;
|
||||
|
||||
simgear::CharArrayIStream caStream(&text[0], text.size());
|
||||
caStream.exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
SG_CHECK_EQUAL(caStream.data(), &text[0]);
|
||||
SG_CHECK_EQUAL(caStream.size(), text.size());
|
||||
|
||||
SG_VERIFY(caStream.get(buf[0])); // get pointer = 1
|
||||
SG_CHECK_EQUAL(buf[0], text[0]);
|
||||
|
||||
caStream.putback(buf[0]); // get pointer = 0
|
||||
SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1
|
||||
|
||||
// std::iostream::operator bool() will return false due to EOF being reached
|
||||
SG_VERIFY(!caStream.read(&buf[1],
|
||||
std::numeric_limits<std::streamsize>::max()));
|
||||
// If badbit had been set, it would have caused an exception to be raised
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
|
||||
// The conversion to std::size_t is safe because caStream.gcount() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by caStream's associated stream buffer, i.e. text.size().
|
||||
n = streamsizeToSize_t(caStream.gcount());
|
||||
SG_CHECK_EQUAL(n, caStream.size() - 1);
|
||||
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
|
||||
|
||||
SG_CHECK_EQUAL(caStream.get(), EOF);
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
|
||||
// Test stream extraction: operator>>()
|
||||
caStream.clear(); // clear the error state flags
|
||||
SG_VERIFY(caStream.seekg(0)); // rewind
|
||||
std::vector<string> expectedWords = {
|
||||
"0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
"ABCDEF",
|
||||
"GHIJK",
|
||||
"LMNOPQ"
|
||||
};
|
||||
string str;
|
||||
|
||||
for (int i = 0; caStream >> str; i++) {
|
||||
SG_CHECK_EQUAL(str, expectedWords[i]);
|
||||
}
|
||||
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
}
|
||||
|
||||
void test_CharArrayOStream_simple()
|
||||
{
|
||||
cerr << "Testing write operations to CharArrayOStream\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
// One could also use an std::vector<char>, but then beware of reallocations!
|
||||
std::unique_ptr<char[]> buf(new char[text.size()]);
|
||||
std::fill_n(buf.get(), text.size(), '\0'); // to ensure reproducible results
|
||||
|
||||
simgear::CharArrayOStream caStream(&buf[0], text.size());
|
||||
|
||||
SG_CHECK_EQUAL(caStream.data(), &buf[0]);
|
||||
SG_CHECK_EQUAL(caStream.size(), text.size());
|
||||
|
||||
SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1
|
||||
SG_CHECK_EQUAL(buf[0], text[0]);
|
||||
|
||||
SG_VERIFY(caStream.seekp(8)); // put pointer = 8
|
||||
// buf[8:23] = text[8:23] (meaning: buf[8] = text[8], ..., buf[22] = text[22])
|
||||
SG_VERIFY(caStream.write(&text[8], 15)); // put pointer = 23
|
||||
buf[1] = 'X'; // write garbage to buf[1]
|
||||
buf[2] = 'Y'; // and to buf[2]
|
||||
SG_VERIFY(caStream.seekp(-22, std::ios_base::cur)); // put pointer = 23-22 = 1
|
||||
SG_VERIFY(caStream.write(&text[1], 7)); // buf[1:8] = text[1:8]
|
||||
|
||||
// The std::ios_base::beg argument is superfluous here---just for testing.
|
||||
SG_VERIFY(caStream.seekp(23, std::ios_base::beg)); // put pointer = 23
|
||||
// Test stream insertion: operator<<()
|
||||
SG_VERIFY(caStream << text.substr(23, 10));
|
||||
SG_VERIFY(caStream.write(&text[33], text.size() - 33)); // all that remains
|
||||
SG_VERIFY(!caStream.put('Z')); // doesn't fit in caStream's buffer
|
||||
SG_VERIFY(caStream.bad()); // put() set the stream's badbit flag
|
||||
|
||||
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
|
||||
}
|
||||
|
||||
void test_CharArrayIOStream_readWriteSeekPutbackEtc()
|
||||
{
|
||||
cerr << "Testing read, write, seek, putback... from/to CharArrayIOStream\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
std::unique_ptr<char[]> buf(new char[text.size()]);
|
||||
std::size_t n;
|
||||
char ch;
|
||||
|
||||
simgear::CharArrayIOStream caStream(&buf[0], text.size());
|
||||
caStream.exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
SG_CHECK_EQUAL(caStream.data(), &buf[0]);
|
||||
SG_CHECK_EQUAL(caStream.size(), text.size());
|
||||
|
||||
SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1
|
||||
SG_CHECK_EQUAL(buf[0], text[0]);
|
||||
SG_VERIFY(caStream.get(ch)); // read it back from buf, get pointer = 1
|
||||
SG_CHECK_EQUAL(ch, text[0]);
|
||||
|
||||
caStream.putback(buf[0]); // get pointer = 0
|
||||
SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1
|
||||
|
||||
SG_VERIFY(caStream.seekp(5));
|
||||
// buf[5:10] = text[5:10] (meaning: buf[5] = text[5], ..., buf[9] = text[9])
|
||||
SG_VERIFY(caStream.write(&text[5], 5)); // put pointer = 10
|
||||
buf[1] = 'X'; // write garbage to buf[1]
|
||||
buf[2] = 'Y'; // and to buf[2]
|
||||
SG_VERIFY(caStream.seekp(-9, std::ios_base::cur)); // put pointer = 10 - 9 = 1
|
||||
SG_VERIFY(caStream.write(&text[1], 4)); // buf[1:5] = text[1:5]
|
||||
|
||||
SG_VERIFY(caStream.seekp(10)); // put pointer = 10
|
||||
// buf[10:] = text[10:]
|
||||
SG_VERIFY(caStream.write(&text[10], text.size() - 10));
|
||||
|
||||
std::unique_ptr<char[]> buf2(new char[caStream.size() - 10]);
|
||||
SG_VERIFY(caStream.seekg(10)); // get pointer = 10
|
||||
// std::iostream::operator bool() will return false due to EOF being reached
|
||||
SG_VERIFY(!caStream.read(&buf2[0],
|
||||
std::numeric_limits<std::streamsize>::max()));
|
||||
// If badbit had been set, it would have caused an exception to be raised
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
|
||||
// The conversion to std::size_t is safe because caStream.gcount() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by caStream's associated stream buffer, i.e. text.size().
|
||||
n = streamsizeToSize_t(caStream.gcount());
|
||||
SG_CHECK_EQUAL(n, caStream.size() - 10);
|
||||
SG_CHECK_EQUAL(caStream.get(), EOF);
|
||||
|
||||
SG_CHECK_EQUAL(string(&buf2[0], caStream.size() - 10),
|
||||
string(&text[10], text.size() - 10));
|
||||
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
test_CharArrayStreambuf_basicOperations();
|
||||
test_CharArrayStreambuf_readOrWriteLargestPossibleAmount();
|
||||
test_CharArrayIStream_simple();
|
||||
test_CharArrayOStream_simple();
|
||||
test_CharArrayIOStream_readWriteSeekPutbackEtc();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/compiler.h>
|
||||
#include <string>
|
||||
#include <ctype.h> // isspace()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib> // for EXIT_SUCCESS
|
||||
|
||||
|
||||
@@ -6,27 +6,33 @@
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
// 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 program is distributed in the hope that it will be useful,
|
||||
// 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 General Public License for more details.
|
||||
// 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.
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <ios> // std::streamsize
|
||||
#include <istream>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <utility> // std::move()
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned(), std::underlying_type
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
#include <cassert>
|
||||
|
||||
@@ -41,6 +47,11 @@
|
||||
using std::string;
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
// Cast an enum value to its underlying type
|
||||
template <typename T>
|
||||
static constexpr typename std::underlying_type<T>::type enumValue(T e) {
|
||||
return static_cast<typename std::underlying_type<T>::type>(e);
|
||||
}
|
||||
|
||||
// Private utility function
|
||||
static string zlibErrorMessage(const z_stream& zstream, int errorCode)
|
||||
@@ -75,38 +86,53 @@ static string zlibErrorMessage(const z_stream& zstream, int errorCode)
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return the largest value that can be represented with zlib's uInt type,
|
||||
// which is the type of z_stream.avail_in and z_stream.avail_out, hence the
|
||||
// function name.
|
||||
static std::size_t zlibMaxChunkSize()
|
||||
// Requirement: 'val' must be non-negative.
|
||||
//
|
||||
// Return:
|
||||
// - 'val' cast as BoundType if it's lower than the largest value BoundType
|
||||
// can represent;
|
||||
// - this largest value otherwise.
|
||||
template<class BoundType, class T>
|
||||
static BoundType clipCast(T val)
|
||||
{
|
||||
uLong flags = ::zlibCompileFlags();
|
||||
std::size_t res;
|
||||
typedef typename std::make_unsigned<T>::type uT;
|
||||
typedef typename std::make_unsigned<BoundType>::type uBoundType;
|
||||
assert(val >= 0); // otherwise, the comparison and cast to uT would be unsafe
|
||||
|
||||
switch (flags & 0x3) {
|
||||
case 0x3:
|
||||
SG_LOG(SG_IO, SG_WARN,
|
||||
"Unknown size for zlib's uInt type (code 3). Will assume 64 bits, "
|
||||
"but the actual value is probably higher.");
|
||||
// No 'break' here, this is intentional.
|
||||
case 0x2:
|
||||
res = 0xFFFFFFFFFFFFFFFF; // 2^64 - 1
|
||||
break;
|
||||
case 0x1:
|
||||
res = 0xFFFFFFFF; // 2^32 - 1
|
||||
break;
|
||||
case 0x0:
|
||||
res = 0xFFFF; // 2^16 - 1
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("It should be impossible to get here.");
|
||||
// Casts to avoid the signed-compare warning; they don't affect the values,
|
||||
// since both are non-negative.
|
||||
if (static_cast<uT>(val) <
|
||||
static_cast<uBoundType>( std::numeric_limits<BoundType>::max() )) {
|
||||
return static_cast<BoundType>(val);
|
||||
} else {
|
||||
return std::numeric_limits<BoundType>::max();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const std::size_t zlibMaxChunk = ::zlibMaxChunkSize();
|
||||
// Requirement: 'size' must be non-negative.
|
||||
//
|
||||
// Return:
|
||||
// - 'size' if it is lower than or equal to std::numeric_limits<uInt>::max();
|
||||
// - std::numeric_limits<uInt>::max() cast as a T otherwise (this is always
|
||||
// possible in a lossless way, since in this case, one has
|
||||
// 0 <= std::numeric_limits<uInt>::max() < size, and 'size' is of type T).
|
||||
//
|
||||
// Note: uInt is the type of z_stream.avail_in and z_stream.avail_out, hence
|
||||
// the function name.
|
||||
template<class T>
|
||||
static T zlibChunk(T size)
|
||||
{
|
||||
typedef typename std::make_unsigned<T>::type uT;
|
||||
assert(size >= 0); // otherwise, the comparison and cast to uT would be unsafe
|
||||
|
||||
if (static_cast<uT>(size) <= std::numeric_limits<uInt>::max()) {
|
||||
return size;
|
||||
} else {
|
||||
// In this case, we are sure that T can represent
|
||||
// std::numeric_limits<uInt>::max(), thus the cast is safe.
|
||||
return static_cast<T>(std::numeric_limits<uInt>::max());
|
||||
}
|
||||
}
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -125,13 +151,13 @@ ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(std::istream& iStream,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: _iStream(iStream),
|
||||
_iStream_p(nullptr),
|
||||
_path(path),
|
||||
_inBuf(inBuf),
|
||||
_inBufSize(inBufSize),
|
||||
_outBuf(outBuf),
|
||||
_outBufSize(outBufSize),
|
||||
_putbackSize(putbackSize)
|
||||
|
||||
{
|
||||
assert(_inBufSize > 0);
|
||||
assert(_putbackSize >= 0); // guaranteed unless the type is changed...
|
||||
@@ -164,6 +190,24 @@ ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(std::istream& iStream,
|
||||
setg(_outBuf, _outBuf, _outBuf);
|
||||
}
|
||||
|
||||
ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(
|
||||
std::unique_ptr<std::istream> iStream_p,
|
||||
const SGPath& path,
|
||||
char* inBuf,
|
||||
std::size_t inBufSize,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: ZlibAbstractIStreambuf(*iStream_p, path, inBuf, inBufSize,
|
||||
outBuf, outBufSize, putbackSize)
|
||||
{
|
||||
// Take ownership of the object. This is a way to ensure that the _iStream
|
||||
// reference stays valid as long as our instance is alive, and that the
|
||||
// corresponding std::istream object is automatically destroyed as soon as
|
||||
// our instance is itself destroyed.
|
||||
_iStream_p = std::move(iStream_p);
|
||||
}
|
||||
|
||||
ZlibAbstractIStreambuf::~ZlibAbstractIStreambuf()
|
||||
{
|
||||
if (_inBufMustBeFreed) {
|
||||
@@ -214,8 +258,8 @@ int ZlibAbstractIStreambuf::underflow()
|
||||
std::size_t
|
||||
ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
|
||||
{
|
||||
std::ptrdiff_t remainingSpaceInOutBuf = _outBuf + _outBufSize -
|
||||
reinterpret_cast<char*>(nextOutPtr);
|
||||
std::ptrdiff_t remainingSpaceInOutBuf = (
|
||||
_outBuf + _outBufSize - reinterpret_cast<char*>(nextOutPtr));
|
||||
assert(remainingSpaceInOutBuf >= 0);
|
||||
|
||||
return static_cast<std::size_t>(remainingSpaceInOutBuf);
|
||||
@@ -226,7 +270,7 @@ ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
|
||||
[[ noreturn ]] void ZlibAbstractIStreambuf::handleZ_BUF_ERROR() const
|
||||
{
|
||||
switch (operationType()) {
|
||||
case OPERATION_TYPE_DECOMPRESSION:
|
||||
case OperationType::DECOMPRESSION:
|
||||
{
|
||||
string message = (_path.isNull()) ?
|
||||
"Got Z_BUF_ERROR from zlib while decompressing a stream. The stream "
|
||||
@@ -237,15 +281,15 @@ ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
|
||||
// When _path.isNull(), sg_location(_path) is equivalent to sg_location()
|
||||
throw sg_io_exception(message, sg_location(_path));
|
||||
}
|
||||
case OPERATION_TYPE_COMPRESSION:
|
||||
case OperationType::COMPRESSION:
|
||||
throw std::logic_error(
|
||||
"Called ZlibAbstractIStreambuf::handleZ_BUF_ERROR() with "
|
||||
"operationType() == OPERATION_TYPE_DECOMPRESSION");
|
||||
"operationType() == ZlibAbstractIStreambuf::OperationType::COMPRESSION");
|
||||
default:
|
||||
throw std::logic_error(
|
||||
"Unexpected operationType() in "
|
||||
"ZlibAbstractIStreambuf::handleZ_BUF_ERROR(): " +
|
||||
std::to_string(operationType()));
|
||||
std::to_string(enumValue(operationType())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,9 +309,7 @@ char* ZlibAbstractIStreambuf::fillOutputBuffer()
|
||||
remainingSpaceInOutBuf = fOB_remainingSpace(_zstream.next_out);
|
||||
|
||||
while (remainingSpaceInOutBuf > 0) {
|
||||
// This does fit in a zlib uInt: that's the whole point of zlibMaxChunk.
|
||||
_zstream.avail_out = static_cast<uInt>(
|
||||
std::min(remainingSpaceInOutBuf, zlibMaxChunk));
|
||||
_zstream.avail_out = clipCast<uInt>(remainingSpaceInOutBuf);
|
||||
|
||||
if (_zstream.avail_in == 0 && !allInputRead) {
|
||||
// Get data from _iStream, store it in _inBuf
|
||||
@@ -327,9 +369,7 @@ bool ZlibAbstractIStreambuf::getInputData()
|
||||
|
||||
// Data already available?
|
||||
if (alreadyAvailable > 0) {
|
||||
_zstream.avail_in = static_cast<uInt>(
|
||||
std::min(static_cast<std::size_t>(alreadyAvailable),
|
||||
zlibMaxChunk));
|
||||
_zstream.avail_in = clipCast<uInt>(alreadyAvailable);
|
||||
return allInputRead;
|
||||
}
|
||||
|
||||
@@ -340,9 +380,8 @@ bool ZlibAbstractIStreambuf::getInputData()
|
||||
|
||||
// Fill the input buffer (as much as possible)
|
||||
while (_inBufEndPtr < _inBuf + _inBufSize && !_iStream.eof()) {
|
||||
std::streamsize nbCharsToRead = std::min(
|
||||
_inBuf + _inBufSize - _inBufEndPtr,
|
||||
std::numeric_limits<std::streamsize>::max()); // max we can pass to read()
|
||||
std::streamsize nbCharsToRead = clipCast<std::streamsize>(
|
||||
_inBuf + _inBufSize - _inBufEndPtr);
|
||||
_iStream.read(_inBufEndPtr, nbCharsToRead);
|
||||
|
||||
if (_iStream.bad()) {
|
||||
@@ -361,10 +400,8 @@ bool ZlibAbstractIStreambuf::getInputData()
|
||||
|
||||
std::ptrdiff_t availableChars =
|
||||
_inBufEndPtr - reinterpret_cast<char*>(_zstream.next_in);
|
||||
assert(availableChars >= 0);
|
||||
_zstream.avail_in = static_cast<uInt>(
|
||||
std::min(static_cast<std::size_t>(availableChars),
|
||||
zlibMaxChunk));
|
||||
// assert(availableChars >= 0); <-- already done in clipCast<uInt>()
|
||||
_zstream.avail_in = clipCast<uInt>(availableChars);
|
||||
|
||||
if (_iStream.eof()) {
|
||||
allInputRead = true;
|
||||
@@ -384,24 +421,34 @@ bool ZlibAbstractIStreambuf::getInputData()
|
||||
// (_outBuf) before being copied to its (hopefully) final destination.
|
||||
std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
{
|
||||
std::size_t remaining = static_cast<std::size_t>(n);
|
||||
std::size_t chunkSize;
|
||||
std::ptrdiff_t avail;
|
||||
const char* origGptr = gptr();
|
||||
// Despite the somewhat misleading footnote 296 of §27.5.3 of the C++11
|
||||
// standard, one can't assume std::size_t to be at least as large as
|
||||
// std::streamsize (64 bits std::streamsize in a 32 bits Windows program).
|
||||
std::streamsize remaining = n;
|
||||
char* origGptr = gptr();
|
||||
char* writePtr = dest; // we'll need dest later -> work with a copy
|
||||
|
||||
// First, let's take data present in our internal buffer (_outBuf)
|
||||
while (remaining > 0) {
|
||||
avail = egptr() - gptr(); // number of available chars in _outBuf
|
||||
// Number of available chars in _outBuf
|
||||
std::ptrdiff_t avail = egptr() - gptr();
|
||||
if (avail == 0) { // our internal buffer is empty
|
||||
break;
|
||||
}
|
||||
|
||||
chunkSize = std::min(remaining, static_cast<std::size_t>(avail));
|
||||
std::copy(gptr(), gptr() + chunkSize, writePtr);
|
||||
gbump(chunkSize);
|
||||
writePtr += chunkSize;
|
||||
remaining -= chunkSize;
|
||||
// We need an int for gbump(), at least in C++11.
|
||||
int chunkSize_i = clipCast<int>(avail);
|
||||
if (chunkSize_i > remaining) {
|
||||
chunkSize_i = static_cast<int>(remaining);
|
||||
}
|
||||
assert(chunkSize_i >= 0);
|
||||
|
||||
std::copy(gptr(), gptr() + chunkSize_i, writePtr);
|
||||
gbump(chunkSize_i);
|
||||
writePtr += chunkSize_i;
|
||||
// This cast is okay because 0 <= chunkSize_i <= remaining, which is an
|
||||
// std::streamsize
|
||||
remaining -= static_cast<std::streamsize>(chunkSize_i);
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
@@ -418,9 +465,10 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
int retCode;
|
||||
|
||||
while (remaining > 0) {
|
||||
chunkSize = std::min(remaining, zlibMaxChunk);
|
||||
// It does fit in a zlib uInt: that's the whole point of zlibMaxChunk.
|
||||
_zstream.avail_out = static_cast<uInt>(chunkSize);
|
||||
std::streamsize chunkSize_s = zlibChunk(remaining);
|
||||
// chunkSize_s > 0 and does fit in a zlib uInt: that's the whole point of
|
||||
// zlibChunk.
|
||||
_zstream.avail_out = static_cast<uInt>(chunkSize_s);
|
||||
|
||||
if (_zstream.avail_in == 0 && !allInputRead) {
|
||||
allInputRead = getInputData();
|
||||
@@ -429,8 +477,9 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
// Make zlib process some data (compress or decompress). This updates
|
||||
// _zstream.{avail,next}_{in,out} (4 fields of the z_stream struct).
|
||||
retCode = zlibProcessData();
|
||||
// chunkSize - _zstream.avail_out is the nb of chars written by zlib
|
||||
remaining -= chunkSize - _zstream.avail_out;
|
||||
// chunkSize_s - _zstream.avail_out is the number of chars written by zlib.
|
||||
// 0 <= _zstream.avail_out <= chunkSize_s, which is an std::streamsize.
|
||||
remaining -= chunkSize_s - static_cast<std::streamsize>(_zstream.avail_out);
|
||||
|
||||
if (retCode == Z_BUF_ERROR) {
|
||||
handleZ_BUF_ERROR(); // doesn't return
|
||||
@@ -444,12 +493,23 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, prepare the putback area.
|
||||
//
|
||||
// We could use reinterpret_cast<char*>(_zstream.next_out) everywhere below,
|
||||
// except it is hardly readable. This points after the latest data we wrote.
|
||||
writePtr = reinterpret_cast<char*>(_zstream.next_out);
|
||||
// Finally, copy chars to the putback area.
|
||||
std::size_t nbPutbackChars = xsgetn_preparePutbackArea(
|
||||
origGptr, dest, reinterpret_cast<char*>(_zstream.next_out));
|
||||
setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
|
||||
_outBuf + _putbackSize, // the buffer for pending,
|
||||
_outBuf + _putbackSize); // available data is empty
|
||||
|
||||
assert(remaining >= 0);
|
||||
assert(n - remaining >= 0);
|
||||
// Total number of chars copied.
|
||||
return n - remaining;
|
||||
}
|
||||
|
||||
// Utility method for xsgetn(): copy some chars to the putback area
|
||||
std::size_t ZlibAbstractIStreambuf::xsgetn_preparePutbackArea(
|
||||
char* origGptr, char* dest, char* writePtr)
|
||||
{
|
||||
// There are two buffers containing characters we potentially have to copy
|
||||
// to the putback area: the one starting at _outBuf and the one starting at
|
||||
// dest. In the following diagram, ***** represents those from _outBuf,
|
||||
@@ -472,38 +532,33 @@ std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
//
|
||||
// [1] This means that the last char represented by a star is at address
|
||||
// origGptr-1.
|
||||
|
||||
// It seems std::ptrdiff_t is the signed counterpart of std::size_t,
|
||||
// therefore this should always hold (even with equality).
|
||||
static_assert(sizeof(std::size_t) >= sizeof(std::ptrdiff_t),
|
||||
"Unexpected: sizeof(std::size_t) < sizeof(std::ptrdiff_t)");
|
||||
assert(writePtr - dest >= 0);
|
||||
std::size_t inDestBuffer = static_cast<std::size_t>(writePtr - dest);
|
||||
|
||||
assert(origGptr - eback() >= 0);
|
||||
std::size_t nbPutbackChars = std::min(
|
||||
static_cast<std::size_t>(origGptr - eback()) + inDestBuffer,
|
||||
_putbackSize);
|
||||
std::size_t nbPutbackCharsToGo = nbPutbackChars;
|
||||
|
||||
// chunkSize has an unsigned type; precomputing it before the following test
|
||||
// wouldn't work.
|
||||
// Are there chars in _outBuf that need to be copied to the putback area?
|
||||
if (nbPutbackChars > inDestBuffer) {
|
||||
chunkSize = nbPutbackChars - inDestBuffer; // yes, this number
|
||||
std::size_t chunkSize = nbPutbackChars - inDestBuffer; // yes, this number
|
||||
std::copy(origGptr - chunkSize, origGptr,
|
||||
_outBuf + _putbackSize - nbPutbackChars);
|
||||
nbPutbackCharsToGo -= chunkSize;
|
||||
}
|
||||
|
||||
// Finally, copy those that are not in _outBuf
|
||||
std::copy(writePtr - nbPutbackCharsToGo, writePtr,
|
||||
_outBuf + _putbackSize - nbPutbackCharsToGo);
|
||||
setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
|
||||
_outBuf + _putbackSize, // the buffer for pending,
|
||||
_outBuf + _putbackSize); // available data is empty
|
||||
|
||||
std::streamsize rem = static_cast<std::streamsize>(remaining);
|
||||
// This is guaranteed because n is of type std::streamsize and the whole
|
||||
// algorithm ensures that 0 <= remaining <= n.
|
||||
assert(rem >= 0);
|
||||
assert(static_cast<std::size_t>(rem) == remaining);
|
||||
assert(n - rem >= 0);
|
||||
// Total number of chars copied.
|
||||
return n - rem;
|
||||
return nbPutbackChars;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
@@ -527,6 +582,24 @@ ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
|
||||
zStreamInit(compressionLevel, format, memStrategy);
|
||||
}
|
||||
|
||||
ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
|
||||
std::unique_ptr<std::istream> iStream_p,
|
||||
const SGPath& path,
|
||||
int compressionLevel,
|
||||
ZLibCompressionFormat format,
|
||||
ZLibMemoryStrategy memStrategy,
|
||||
char* inBuf,
|
||||
std::size_t inBufSize,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: ZlibCompressorIStreambuf(*iStream_p, path, compressionLevel, format,
|
||||
memStrategy, inBuf, inBufSize, outBuf, outBufSize,
|
||||
putbackSize)
|
||||
{
|
||||
_iStream_p = std::move(iStream_p); // take ownership of the object
|
||||
}
|
||||
|
||||
ZlibCompressorIStreambuf::~ZlibCompressorIStreambuf()
|
||||
{
|
||||
int retCode = deflateEnd(&_zstream); // deallocate the z_stream struct
|
||||
@@ -540,7 +613,7 @@ ZlibCompressorIStreambuf::~ZlibCompressorIStreambuf()
|
||||
ZlibAbstractIStreambuf::OperationType
|
||||
ZlibCompressorIStreambuf::operationType() const
|
||||
{
|
||||
return OPERATION_TYPE_COMPRESSION;
|
||||
return OperationType::COMPRESSION;
|
||||
}
|
||||
|
||||
void ZlibCompressorIStreambuf::zStreamInit(int compressionLevel,
|
||||
@@ -549,30 +622,30 @@ void ZlibCompressorIStreambuf::zStreamInit(int compressionLevel,
|
||||
{
|
||||
int windowBits, memLevel;
|
||||
|
||||
// Intentionally not listing ZLIB_COMPRESSION_FORMAT_AUTODETECT here (it is
|
||||
// Intentionally not listing ZLibCompressionFormat::AUTODETECT here (it is
|
||||
// only for decompression!)
|
||||
switch (format) {
|
||||
case ZLIB_COMPRESSION_FORMAT_ZLIB:
|
||||
case ZLibCompressionFormat::ZLIB:
|
||||
windowBits = 15;
|
||||
break;
|
||||
case ZLIB_COMPRESSION_FORMAT_GZIP:
|
||||
case ZLibCompressionFormat::GZIP:
|
||||
windowBits = 31;
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("Unexpected compression format: " +
|
||||
std::to_string(format));
|
||||
std::to_string(enumValue(format)));
|
||||
}
|
||||
|
||||
switch (memStrategy) {
|
||||
case ZLIB_FAVOR_MEMORY_OVER_SPEED:
|
||||
case ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED:
|
||||
memLevel = 8;
|
||||
break;
|
||||
case ZLIB_FAVOR_SPEED_OVER_MEMORY:
|
||||
case ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY:
|
||||
memLevel = 9;
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("Unexpected memory strategy: " +
|
||||
std::to_string(memStrategy));
|
||||
std::to_string(enumValue(memStrategy)));
|
||||
}
|
||||
|
||||
_zstream.zalloc = Z_NULL; // No custom memory allocation routines
|
||||
@@ -615,6 +688,21 @@ ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
|
||||
zStreamInit(format);
|
||||
}
|
||||
|
||||
ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
|
||||
std::unique_ptr<std::istream> iStream_p,
|
||||
const SGPath& path,
|
||||
ZLibCompressionFormat format,
|
||||
char* inBuf,
|
||||
std::size_t inBufSize,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: ZlibDecompressorIStreambuf(*iStream_p, path, format, inBuf, inBufSize,
|
||||
outBuf, outBufSize, putbackSize)
|
||||
{
|
||||
_iStream_p = std::move(iStream_p); // take ownership of the object
|
||||
}
|
||||
|
||||
ZlibDecompressorIStreambuf::~ZlibDecompressorIStreambuf()
|
||||
{
|
||||
int retCode = inflateEnd(&_zstream); // deallocate the z_stream struct
|
||||
@@ -628,7 +716,7 @@ ZlibDecompressorIStreambuf::~ZlibDecompressorIStreambuf()
|
||||
ZlibAbstractIStreambuf::OperationType
|
||||
ZlibDecompressorIStreambuf::operationType() const
|
||||
{
|
||||
return OPERATION_TYPE_DECOMPRESSION;
|
||||
return OperationType::DECOMPRESSION;
|
||||
}
|
||||
|
||||
void ZlibDecompressorIStreambuf::zStreamInit(ZLibCompressionFormat format)
|
||||
@@ -636,18 +724,18 @@ void ZlibDecompressorIStreambuf::zStreamInit(ZLibCompressionFormat format)
|
||||
int windowBits;
|
||||
|
||||
switch (format) {
|
||||
case ZLIB_COMPRESSION_FORMAT_ZLIB:
|
||||
case ZLibCompressionFormat::ZLIB:
|
||||
windowBits = 15;
|
||||
break;
|
||||
case ZLIB_COMPRESSION_FORMAT_GZIP:
|
||||
case ZLibCompressionFormat::GZIP:
|
||||
windowBits = 31;
|
||||
break;
|
||||
case ZLIB_COMPRESSION_FORMAT_AUTODETECT:
|
||||
case ZLibCompressionFormat::AUTODETECT:
|
||||
windowBits = 47; // 47 = 32 + 15
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("Unexpected compression format: " +
|
||||
std::to_string(format));
|
||||
std::to_string(enumValue(format)));
|
||||
}
|
||||
|
||||
_zstream.zalloc = Z_NULL; // No custom memory allocation routines
|
||||
@@ -684,10 +772,31 @@ ZlibCompressorIStream::ZlibCompressorIStream(std::istream& iStream,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: _streamBuf(iStream, path, compressionLevel, format, memStrategy, inBuf,
|
||||
: std::istream(nullptr),
|
||||
_streamBuf(iStream, path, compressionLevel, format, memStrategy, inBuf,
|
||||
inBufSize, outBuf, outBufSize, putbackSize)
|
||||
{
|
||||
rdbuf(&_streamBuf); // Associate _streamBuf to 'this'
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
ZlibCompressorIStream::ZlibCompressorIStream(
|
||||
std::unique_ptr<std::istream> iStream_p,
|
||||
const SGPath& path,
|
||||
int compressionLevel,
|
||||
ZLibCompressionFormat format,
|
||||
ZLibMemoryStrategy memStrategy,
|
||||
char* inBuf,
|
||||
std::size_t inBufSize,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: std::istream(nullptr),
|
||||
_streamBuf(std::move(iStream_p), path, compressionLevel, format,
|
||||
memStrategy, inBuf, inBufSize, outBuf, outBufSize, putbackSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
ZlibCompressorIStream::~ZlibCompressorIStream()
|
||||
@@ -705,10 +814,29 @@ ZlibDecompressorIStream::ZlibDecompressorIStream(std::istream& iStream,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: _streamBuf(iStream, path, format, inBuf, inBufSize, outBuf, outBufSize,
|
||||
: std::istream(nullptr),
|
||||
_streamBuf(iStream, path, format, inBuf, inBufSize, outBuf, outBufSize,
|
||||
putbackSize)
|
||||
{
|
||||
rdbuf(&_streamBuf); // Associate _streamBuf to 'this'
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
ZlibDecompressorIStream::ZlibDecompressorIStream(
|
||||
std::unique_ptr<std::istream> iStream_p,
|
||||
const SGPath& path,
|
||||
ZLibCompressionFormat format,
|
||||
char* inBuf,
|
||||
std::size_t inBufSize,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize,
|
||||
std::size_t putbackSize)
|
||||
: std::istream(nullptr),
|
||||
_streamBuf(std::move(iStream_p), path, format, inBuf, inBufSize,
|
||||
outBuf, outBufSize, putbackSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
ZlibDecompressorIStream::~ZlibDecompressorIStream()
|
||||
|
||||
@@ -6,25 +6,28 @@
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
// 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 program is distributed in the hope that it will be useful,
|
||||
// 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 General Public License for more details.
|
||||
// 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.
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef SG_ZLIBSTREAM_HXX
|
||||
#define SG_ZLIBSTREAM_HXX
|
||||
|
||||
#include <iosfwd>
|
||||
#include <ios> // std::streamsize
|
||||
#include <istream>
|
||||
#include <streambuf>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <zlib.h> // struct z_stream
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
@@ -97,15 +100,15 @@
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
enum ZLibCompressionFormat {
|
||||
ZLIB_COMPRESSION_FORMAT_ZLIB = 0,
|
||||
ZLIB_COMPRESSION_FORMAT_GZIP,
|
||||
ZLIB_COMPRESSION_FORMAT_AUTODETECT
|
||||
enum class ZLibCompressionFormat {
|
||||
ZLIB = 0,
|
||||
GZIP,
|
||||
AUTODETECT
|
||||
};
|
||||
|
||||
enum ZLibMemoryStrategy {
|
||||
ZLIB_FAVOR_MEMORY_OVER_SPEED = 0,
|
||||
ZLIB_FAVOR_SPEED_OVER_MEMORY
|
||||
enum class ZLibMemoryStrategy {
|
||||
FAVOR_MEMORY_OVER_SPEED = 0,
|
||||
FAVOR_SPEED_OVER_MEMORY
|
||||
};
|
||||
|
||||
// Abstract base class for both the compressor and decompressor stream buffers.
|
||||
@@ -144,14 +147,27 @@ public:
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0);
|
||||
|
||||
// Alternate constructor with sink semantics for the “source” std::istream.
|
||||
// When used, the class takes ownership of the std::istream instance pointed
|
||||
// to by the first constructor argument, and keeps it alive as long as the
|
||||
// object this constructor is for is itself alive.
|
||||
explicit ZlibAbstractIStreambuf(std::unique_ptr<std::istream> iStream_p,
|
||||
const SGPath& path = SGPath(),
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0);
|
||||
|
||||
ZlibAbstractIStreambuf(const ZlibAbstractIStreambuf&) = delete;
|
||||
ZlibAbstractIStreambuf& operator=(const ZlibAbstractIStreambuf&) = delete;
|
||||
~ZlibAbstractIStreambuf();
|
||||
virtual ~ZlibAbstractIStreambuf();
|
||||
|
||||
protected:
|
||||
enum OperationType {
|
||||
OPERATION_TYPE_COMPRESSION = 0,
|
||||
OPERATION_TYPE_DECOMPRESSION
|
||||
enum class OperationType {
|
||||
COMPRESSION = 0,
|
||||
DECOMPRESSION
|
||||
};
|
||||
|
||||
virtual OperationType operationType() const = 0;
|
||||
@@ -166,6 +182,11 @@ protected:
|
||||
|
||||
// The input stream, from which data is read before being processed by zlib
|
||||
std::istream& _iStream;
|
||||
// Pointer to the same, used when calling the constructor that takes an
|
||||
// std::unique_ptr<std::istream> as its first argument; empty
|
||||
// std::unique_ptr object otherwise.
|
||||
std::unique_ptr<std::istream> _iStream_p;
|
||||
|
||||
// Corresponding path, if any (default-constructed SGPath instance otherwise)
|
||||
const SGPath _path;
|
||||
// Structure used to communicate with zlib
|
||||
@@ -174,12 +195,15 @@ protected:
|
||||
private:
|
||||
// Callback whose role is to refill the output buffer when it's empty and
|
||||
// the “client” tries to read more.
|
||||
int underflow() override;
|
||||
virtual int underflow() override;
|
||||
// Optional override when subclassing std::streambuf. This is the most
|
||||
// efficient way of reading several characters (as soon as we've emptied the
|
||||
// output buffer, data is written by zlib directly to the destination
|
||||
// buffer).
|
||||
std::streamsize xsgetn(char* dest, std::streamsize n) override;
|
||||
virtual std::streamsize xsgetn(char* dest, std::streamsize n) override;
|
||||
// Utility method for xsgetn()
|
||||
std::size_t xsgetn_preparePutbackArea(char* origGptr, char* dest,
|
||||
char* writePtr);
|
||||
// Make sure there is data to read in the input buffer, or signal EOF.
|
||||
bool getInputData();
|
||||
// Utility method for fillOutputBuffer()
|
||||
@@ -267,32 +291,46 @@ public:
|
||||
// the highest compression speed but worst compression
|
||||
// ratio, and 9 the highest compression ratio but lowest
|
||||
// compression speed.
|
||||
// format either ZLIB_COMPRESSION_FORMAT_ZLIB or
|
||||
// ZLIB_COMPRESSION_FORMAT_GZIP
|
||||
// memStrategy either ZLIB_FAVOR_MEMORY_OVER_SPEED or
|
||||
// ZLIB_FAVOR_SPEED_OVER_MEMORY
|
||||
// format either ZLibCompressionFormat::ZLIB or
|
||||
// ZLibCompressionFormat::GZIP
|
||||
// memStrategy either ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED or
|
||||
// ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY
|
||||
explicit ZlibCompressorIStreambuf(
|
||||
std::istream& iStream,
|
||||
const SGPath& path = SGPath(),
|
||||
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
||||
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
ZLibMemoryStrategy memStrategy = ZLIB_FAVOR_SPEED_OVER_MEMORY,
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0);
|
||||
|
||||
// Alternate constructor with sink semantics for the “source” std::istream.
|
||||
explicit ZlibCompressorIStreambuf(
|
||||
std::unique_ptr<std::istream> _iStream_p,
|
||||
const SGPath& path = SGPath(),
|
||||
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0);
|
||||
|
||||
ZlibCompressorIStreambuf(const ZlibCompressorIStreambuf&) = delete;
|
||||
ZlibCompressorIStreambuf& operator=(const ZlibCompressorIStreambuf&) = delete;
|
||||
~ZlibCompressorIStreambuf();
|
||||
virtual ~ZlibCompressorIStreambuf();
|
||||
|
||||
protected:
|
||||
OperationType operationType() const override;
|
||||
virtual OperationType operationType() const override;
|
||||
// Initialize the z_stream struct used by zlib
|
||||
void zStreamInit(int compressionLevel, ZLibCompressionFormat format,
|
||||
ZLibMemoryStrategy memStrategy);
|
||||
// Call zlib's deflate() function to compress data.
|
||||
int zlibProcessData() override;
|
||||
virtual int zlibProcessData() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -307,27 +345,39 @@ class ZlibDecompressorIStreambuf: public ZlibAbstractIStreambuf
|
||||
public:
|
||||
// Same parameters as for ZlibAbstractIStreambuf, except:
|
||||
//
|
||||
// format ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
// ZLIB_COMPRESSION_FORMAT_GZIP or
|
||||
// ZLIB_COMPRESSION_FORMAT_AUTODETECT
|
||||
// format ZLibCompressionFormat::ZLIB,
|
||||
// ZLibCompressionFormat::GZIP or
|
||||
// ZLibCompressionFormat::AUTODETECT
|
||||
explicit ZlibDecompressorIStreambuf(
|
||||
std::istream& iStream,
|
||||
const SGPath& path = SGPath(),
|
||||
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0); // default optimized for speed
|
||||
|
||||
// Alternate constructor with sink semantics for the “source” std::istream.
|
||||
explicit ZlibDecompressorIStreambuf(
|
||||
std::unique_ptr<std::istream> _iStream_p,
|
||||
const SGPath& path = SGPath(),
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0); // default optimized for speed
|
||||
|
||||
ZlibDecompressorIStreambuf(const ZlibDecompressorIStreambuf&) = delete;
|
||||
ZlibDecompressorIStreambuf& operator=(const ZlibDecompressorIStreambuf&)
|
||||
= delete;
|
||||
~ZlibDecompressorIStreambuf();
|
||||
virtual ~ZlibDecompressorIStreambuf();
|
||||
|
||||
protected:
|
||||
OperationType operationType() const override;
|
||||
virtual OperationType operationType() const override;
|
||||
void zStreamInit(ZLibCompressionFormat format);
|
||||
int zlibProcessData() override;
|
||||
virtual int zlibProcessData() override;
|
||||
};
|
||||
|
||||
// std::istream subclass for compressing data. Input data is obtained from an
|
||||
@@ -349,16 +399,30 @@ public:
|
||||
std::istream& iStream,
|
||||
const SGPath& path = SGPath(),
|
||||
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
||||
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
ZLibMemoryStrategy memStrategy = ZLIB_FAVOR_SPEED_OVER_MEMORY,
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0); // default optimized for speed
|
||||
|
||||
// Alternate constructor with sink semantics for the “source” std::istream.
|
||||
explicit ZlibCompressorIStream(
|
||||
std::unique_ptr<std::istream> _iStream_p,
|
||||
const SGPath& path = SGPath(),
|
||||
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0); // default optimized for speed
|
||||
|
||||
ZlibCompressorIStream(const ZlibCompressorIStream&) = delete;
|
||||
ZlibCompressorIStream& operator=(const ZlibCompressorIStream&) = delete;
|
||||
~ZlibCompressorIStream();
|
||||
virtual ~ZlibCompressorIStream();
|
||||
|
||||
private:
|
||||
ZlibCompressorIStreambuf _streamBuf;
|
||||
@@ -382,15 +446,27 @@ public:
|
||||
explicit ZlibDecompressorIStream(
|
||||
std::istream& iStream,
|
||||
const SGPath& path = SGPath(),
|
||||
ZLibCompressionFormat format = ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0); // default optimized for speed
|
||||
|
||||
// Alternate constructor with sink semantics for the “source” std::istream.
|
||||
explicit ZlibDecompressorIStream(
|
||||
std::unique_ptr<std::istream> _iStream_p,
|
||||
const SGPath& path = SGPath(),
|
||||
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
||||
char* inBuf = nullptr,
|
||||
std::size_t inBufSize = 262144,
|
||||
char* outBuf = nullptr,
|
||||
std::size_t outBufSize = 262144,
|
||||
std::size_t putbackSize = 0); // default optimized for speed
|
||||
|
||||
ZlibDecompressorIStream(const ZlibDecompressorIStream&) = delete;
|
||||
ZlibDecompressorIStream& operator=(const ZlibDecompressorIStream&) = delete;
|
||||
~ZlibDecompressorIStream();
|
||||
virtual ~ZlibDecompressorIStream();
|
||||
|
||||
private:
|
||||
ZlibDecompressorIStreambuf _streamBuf;
|
||||
|
||||
@@ -4,36 +4,39 @@
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
// 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 program is distributed in the hope that it will be useful,
|
||||
// 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 General Public License for more details.
|
||||
// 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.
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <ios> // std::basic_ios, std::streamsize...
|
||||
#include <iostream> // std::ios_base, std::cerr, etc.
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <utility> // std::move()
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <functional> // std::bind()
|
||||
#include <cassert>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstring> // strcmp()
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using traits = std::char_traits<char>;
|
||||
#include <zlib.h> // Z_BEST_COMPRESSION
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
@@ -41,6 +44,24 @@ using traits = std::char_traits<char>;
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
|
||||
|
||||
// Safely convert a non-negative std::streamsize into an std::size_t. If
|
||||
// impossible, bail out.
|
||||
static std::size_t streamsizeToSize_t(std::streamsize n)
|
||||
{
|
||||
SG_CHECK_GE(n, 0);
|
||||
SG_CHECK_LE(static_cast<uStreamSize>(n),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
|
||||
return static_cast<std::size_t>(n);
|
||||
}
|
||||
|
||||
// In many tests below, I use very small buffer sizes. Of course, this is bad
|
||||
// for performance. The reason I do it this way is simply because it better
|
||||
// exercises the code we want to *test* here (we are more likely to find bugs
|
||||
@@ -48,8 +69,8 @@ using traits = std::char_traits<char>;
|
||||
// you don't need the putback feature in non-test code, best performance is
|
||||
// achieved with putback size = 0.
|
||||
//
|
||||
// I suggest you read roundTripWithIStreams() below to see how to use the
|
||||
// classes most efficiently (especially the comments!).
|
||||
// I suggest reading test_IStreamConstructorWithSinkSemantics() below to see
|
||||
// how to use the classes efficiently.
|
||||
|
||||
static std::default_random_engine randomNumbersGenerator;
|
||||
|
||||
@@ -177,9 +198,9 @@ void test_StreambufBasicOperations()
|
||||
static constexpr std::size_t compOutBufSize = 4;
|
||||
static constexpr std::size_t compPutbackSize = 0;
|
||||
simgear::ZlibCompressorIStreambuf compSBuf(
|
||||
text_ss, SGPath(), 8, simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
simgear::ZLIB_FAVOR_SPEED_OVER_MEMORY, nullptr, compInBufSize, nullptr,
|
||||
compOutBufSize, compPutbackSize);
|
||||
text_ss, SGPath(), 8, simgear::ZLibCompressionFormat::ZLIB,
|
||||
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
||||
nullptr, compInBufSize, nullptr, compOutBufSize, compPutbackSize);
|
||||
std::stringstream compressedOutput_ss;
|
||||
compressedOutput_ss << &compSBuf;
|
||||
|
||||
@@ -187,7 +208,7 @@ void test_StreambufBasicOperations()
|
||||
static constexpr std::size_t decompOutBufSize = 4;
|
||||
static constexpr std::size_t decompPutbackSize = 2;
|
||||
simgear::ZlibDecompressorIStreambuf decompSBuf(
|
||||
compressedOutput_ss, SGPath(), simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
compressedOutput_ss, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
|
||||
nullptr, decompInBufSize, nullptr, decompOutBufSize, decompPutbackSize);
|
||||
|
||||
int ch = decompSBuf.sgetc();
|
||||
@@ -214,7 +235,9 @@ void test_StreambufBasicOperations()
|
||||
// Most efficient way (with the underlying xsgetn()) to read several chars
|
||||
// at once.
|
||||
std::streamsize n = decompSBuf.sgetn(buf, bufSize);
|
||||
SG_VERIFY(n == bufSize && string(buf, bufSize) == "3456789abc");
|
||||
SG_CHECK_EQUAL(n, bufSize);
|
||||
SG_CHECK_EQUAL(string(buf, static_cast<std::size_t>(bufSize)),
|
||||
"3456789abc");
|
||||
|
||||
ch = decompSBuf.sungetc(); // same as sputbackc(), except no value to check
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'c');
|
||||
@@ -226,15 +249,17 @@ void test_StreambufBasicOperations()
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
|
||||
n = decompSBuf.sgetn(buf, bufSize);
|
||||
SG_VERIFY(n == bufSize && string(buf, bufSize) == "bcdefghijk");
|
||||
SG_CHECK_EQUAL(n, bufSize);
|
||||
SG_CHECK_EQUAL(string(buf, static_cast<std::size_t>(bufSize)),
|
||||
"bcdefghijk");
|
||||
|
||||
ch = decompSBuf.sungetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'k');
|
||||
|
||||
static char buf2[64];
|
||||
n = decompSBuf.sgetn(buf2, sizeof(buf2));
|
||||
SG_VERIFY(n == 36 && string(buf2, n) == "klmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ");
|
||||
SG_CHECK_EQUAL(n, 36);
|
||||
SG_CHECK_EQUAL(string(buf2, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ");
|
||||
|
||||
ch = decompSBuf.sbumpc();
|
||||
SG_CHECK_EQUAL(ch, EOF);
|
||||
@@ -268,6 +293,60 @@ string compress(const string& dataToCompress,
|
||||
return compressedData_ss.str();
|
||||
}
|
||||
|
||||
// Test simgear::ZlibDecompressorIStreambuf::[x]sgetn(), asking the largest
|
||||
// possible amount of chars every time it is called (i.e., the largest value
|
||||
// that can be represented by std::streamsize).
|
||||
void test_ZlibDecompressorIStreambuf_readLargestPossibleAmount()
|
||||
{
|
||||
// Nothing special with these values
|
||||
constexpr std::size_t maxDataSize = 8192;
|
||||
std::istringstream input_ss(randomString(4096, maxDataSize));
|
||||
|
||||
simgear::ZlibCompressorIStream compIStream(
|
||||
input_ss, // input stream
|
||||
SGPath(), // this stream is not associated to a file
|
||||
9, // compression level
|
||||
simgear::ZLibCompressionFormat::ZLIB,
|
||||
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
||||
nullptr, // dynamically allocate the input buffer
|
||||
230, // input buffer size
|
||||
nullptr, // dynamically allocate the output buffer
|
||||
120, // output buffer size
|
||||
1 // putback size
|
||||
);
|
||||
|
||||
// Decompressor stream buffer (std::streambuf subclass) that gets input data
|
||||
// from our compressor 'compIStream' (std::istream subclass)
|
||||
simgear::ZlibDecompressorIStreambuf decompSBuf(
|
||||
compIStream, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
|
||||
nullptr, 150, nullptr, 175, 2);
|
||||
|
||||
std::unique_ptr<char[]> buf(new char[maxDataSize]);
|
||||
std::ostringstream roundTripResult_ss;
|
||||
std::streamsize totalCharsToRead = input_ss.str().size();
|
||||
|
||||
while (totalCharsToRead > 0) {
|
||||
// Ask sgetn() the largest possible amount of chars. Of course, we know we
|
||||
// can't get more than maxDataSize, but this does exercise the code in
|
||||
// interesting ways due to the various types involved (zlib's uInt,
|
||||
// std::size_t and std::streamsize, which have various sizes depending on
|
||||
// the platform).
|
||||
std::streamsize nbCharsRead = decompSBuf.sgetn(
|
||||
&buf[0], std::numeric_limits<std::streamsize>::max());
|
||||
if (nbCharsRead == 0) {
|
||||
break; // no more data
|
||||
}
|
||||
|
||||
// The conversion to std::size_t is safe because decompSBuf.sgetn()
|
||||
// returned a non-negative value which, in this case, can't exceed
|
||||
// maxDataSize.
|
||||
roundTripResult_ss << string(&buf[0], streamsizeToSize_t((nbCharsRead)));
|
||||
}
|
||||
|
||||
SG_CHECK_EQUAL(decompSBuf.sgetc(), EOF);
|
||||
SG_CHECK_EQUAL(roundTripResult_ss.str(), input_ss.str());
|
||||
}
|
||||
|
||||
void test_formattedInputFromDecompressor()
|
||||
{
|
||||
cerr << "Testing ZlibDecompressorIStream >> std::string\n";
|
||||
@@ -275,12 +354,12 @@ void test_formattedInputFromDecompressor()
|
||||
static char inBuf[6];
|
||||
static char outBuf[15];
|
||||
string compressed = compress(
|
||||
lipsum, simgear::ZLIB_COMPRESSION_FORMAT_ZLIB, Z_BEST_COMPRESSION,
|
||||
simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED, /* putback size */ 0);
|
||||
lipsum, simgear::ZLibCompressionFormat::ZLIB, Z_BEST_COMPRESSION,
|
||||
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED, /* putback size */ 0);
|
||||
std::istringstream compressed_ss(compressed);
|
||||
|
||||
simgear::ZlibDecompressorIStream decompressor(
|
||||
compressed_ss, SGPath(), simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
compressed_ss, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
|
||||
inBuf, sizeof(inBuf), outBuf, sizeof(outBuf), /* putback size */ 1);
|
||||
decompressor.exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
@@ -319,15 +398,15 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
|
||||
|
||||
simgear::ZlibCompressorIStream compressor(
|
||||
text_ss, SGPath(), Z_BEST_COMPRESSION,
|
||||
simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED,
|
||||
simgear::ZLibCompressionFormat::ZLIB,
|
||||
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
|
||||
compInBuf, sizeof(compInBuf), compOutBuf, sizeof(compOutBuf),
|
||||
/* putback size */ 0);
|
||||
compressor.exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
// Use the compressor (subclass of std::istream) as input to the decompressor
|
||||
simgear::ZlibDecompressorIStream decompressor(
|
||||
compressor, SGPath(), simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
compressor, SGPath(), simgear::ZLibCompressionFormat::ZLIB,
|
||||
decompInBuf, sizeof(decompInBuf), decompOutBuf, sizeof(decompOutBuf),
|
||||
/* putback size */ 3);
|
||||
decompressor.exceptions(std::ios_base::badbit);
|
||||
@@ -354,6 +433,14 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
|
||||
decompressor.putback('Z');
|
||||
} catch (std::ios_base::failure) {
|
||||
gotException = true;
|
||||
} catch (const std::exception& e) {
|
||||
// gcc fails to catch std::ios_base::failure due to an inconsistent C++11
|
||||
// ABI between headers and libraries. See bug#66145 for more details.
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145
|
||||
if (!strcmp(e.what(), "basic_ios::clear"))
|
||||
gotException = true;
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
SG_VERIFY(gotException && decompressor.bad());
|
||||
}
|
||||
@@ -404,7 +491,10 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
|
||||
string rest(buf2, nbCharsRead);
|
||||
do {
|
||||
decompressor.read(buf2, sizeof(buf2));
|
||||
rest += string(buf2, decompressor.gcount());
|
||||
// The conversion to std::size_t is safe because decompressor.read()
|
||||
// returns a non-negative value which, in this case, can't exceed
|
||||
// sizeof(buf2).
|
||||
rest += string(buf2, streamsizeToSize_t(decompressor.gcount()));
|
||||
} while (decompressor);
|
||||
|
||||
SG_CHECK_EQUAL(rest, " LMNOPQ");
|
||||
@@ -417,7 +507,6 @@ void test_ZlibDecompressorIStream_readPutbackEtc()
|
||||
// Utility function: parametrized round-trip test with a compressor +
|
||||
// decompressor pipeline.
|
||||
//
|
||||
//
|
||||
// Note: this is nice conceptually, allows to keep memory use constant even in
|
||||
// case an arbitrary amount of data is passed through, and exercises the
|
||||
// stream buffer classes well, however this technique is more than twice
|
||||
@@ -442,7 +531,7 @@ void roundTripWithIStreams(
|
||||
{
|
||||
const simgear::ZLibCompressionFormat decompFormat =
|
||||
(useAutoFormatForDecompression) ?
|
||||
simgear::ZLIB_COMPRESSION_FORMAT_AUTODETECT : compressionFormat;
|
||||
simgear::ZLibCompressionFormat::AUTODETECT : compressionFormat;
|
||||
|
||||
std::istringstream lipsum_ss(lipsum);
|
||||
// This tests the optional dynamic buffer allocation in ZlibAbstractIStreambuf
|
||||
@@ -482,11 +571,12 @@ void test_RoundTripMultiWithIStreams()
|
||||
const std::size_t compPutbackSize = 1;
|
||||
const std::size_t decompPutbackSize = 1;
|
||||
|
||||
for (auto format: {simgear::ZLIB_COMPRESSION_FORMAT_ZLIB,
|
||||
simgear::ZLIB_COMPRESSION_FORMAT_GZIP}) {
|
||||
for (auto format: {simgear::ZLibCompressionFormat::ZLIB,
|
||||
simgear::ZLibCompressionFormat::GZIP}) {
|
||||
for (int compressionLevel: {1, 4, 7, 9}) {
|
||||
for (auto memStrategy: {simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED,
|
||||
simgear::ZLIB_FAVOR_SPEED_OVER_MEMORY}) {
|
||||
for (auto memStrategy: {
|
||||
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
|
||||
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY}) {
|
||||
for (std::size_t compInBufSize: {3, 4}) {
|
||||
for (std::size_t compOutBufSize: {3, 5}) {
|
||||
for (std::size_t decompInBufSize: {3, 4}) {
|
||||
@@ -505,9 +595,10 @@ void test_RoundTripMultiWithIStreams()
|
||||
}
|
||||
|
||||
{
|
||||
const auto format = simgear::ZLIB_COMPRESSION_FORMAT_ZLIB;
|
||||
const auto format = simgear::ZLibCompressionFormat::ZLIB;
|
||||
const int compressionLevel = Z_DEFAULT_COMPRESSION;
|
||||
const auto memStrategy = simgear::ZLIB_FAVOR_SPEED_OVER_MEMORY;
|
||||
const auto memStrategy =
|
||||
simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY;
|
||||
|
||||
for (std::size_t compInBufSize: {3, 4, 31, 256, 19475}) {
|
||||
for (std::size_t compOutBufSize: {3, 5, 9, 74, 4568}) {
|
||||
@@ -537,11 +628,12 @@ void test_RoundTripMultiWithIStreams()
|
||||
for (std::size_t compPutbackSize: {25, 40, 105}) {
|
||||
for (std::size_t decompPutbackSize: {30, 60, 81}) {
|
||||
const simgear::ZLibCompressionFormat compFormat = (i++ % 2) ?
|
||||
simgear::ZLIB_COMPRESSION_FORMAT_ZLIB :
|
||||
simgear::ZLIB_COMPRESSION_FORMAT_GZIP;
|
||||
simgear::ZLibCompressionFormat::ZLIB :
|
||||
simgear::ZLibCompressionFormat::GZIP;
|
||||
|
||||
roundTripWithIStreams(
|
||||
compFormat, Z_BEST_COMPRESSION, simgear::ZLIB_FAVOR_MEMORY_OVER_SPEED,
|
||||
compFormat, Z_BEST_COMPRESSION,
|
||||
simgear::ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED,
|
||||
compInBufSize, compOutBufSize, decompInBufSize, decompOutBufSize,
|
||||
compPutbackSize, decompPutbackSize,
|
||||
/* automatic format detection for decompression */ true);
|
||||
@@ -550,13 +642,91 @@ void test_RoundTripMultiWithIStreams()
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function showing how to return a (unique_ptr to a)
|
||||
// ZlibCompressorIStream instance, that keeps a reference to its data source
|
||||
// as long as the ZlibCompressorIStream instance is alive. Thus, calling code
|
||||
// doesn't have to worry about the lifetime of said data source (here, an
|
||||
// std::istringstream instance).
|
||||
std::unique_ptr<simgear::ZlibCompressorIStream>
|
||||
IStreamConstructorWithSinkSemantics_compressorFactory(const string& str)
|
||||
{
|
||||
std::unique_ptr<std::istringstream> iss(new std::istringstream(str));
|
||||
|
||||
// The returned compressor object retains a “reference” (of unique_ptr type)
|
||||
// to the std::istringstream object pointed to by 'iss' as long as it is
|
||||
// alive. When the returned compressor object (wrapped in a unique_ptr) is
|
||||
// destroyed, this std::istringstream object will be automatically
|
||||
// destroyed too.
|
||||
//
|
||||
// Note: it's an implementation detail, but this test also indirectly
|
||||
// exercises the ZlibCompressorIStreambuf constructor taking an
|
||||
// argument of type std::unique_ptr<std::istream>.
|
||||
return std::unique_ptr<simgear::ZlibCompressorIStream>(
|
||||
new simgear::ZlibCompressorIStream(std::move(iss)));
|
||||
}
|
||||
|
||||
void test_IStreamConstructorWithSinkSemantics()
|
||||
{
|
||||
cerr << "Testing the unique_ptr-based ZlibCompressorIStream constructor\n";
|
||||
|
||||
string someString = randomString(4096, 8192); // arbitrary values
|
||||
// This shows how to get a new compressor or decompressor object from a
|
||||
// factory function. Of course, we could create the object directly on the
|
||||
// stack without using a separate function!
|
||||
std::unique_ptr<simgear::ZlibCompressorIStream> compressor =
|
||||
IStreamConstructorWithSinkSemantics_compressorFactory(someString);
|
||||
compressor->exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
// Use the compressor as input to the decompressor (pipeline). The
|
||||
// decompressor uses read() with chunks that are as large as possible given
|
||||
// the available space in its input buffer. These read() calls are served by
|
||||
// ZlibCompressorIStreambuf::xsgetn(), which is efficient. We won't need the
|
||||
// compressor afterwards, so let's just std::move() its unique_ptr.
|
||||
simgear::ZlibDecompressorIStream decompressor(std::move(compressor));
|
||||
decompressor.exceptions(std::ios_base::badbit);
|
||||
|
||||
std::ostringstream roundTripResult;
|
||||
// Of course, you may want to adjust bufSize depending on the application.
|
||||
static constexpr std::size_t bufSize = 1024;
|
||||
std::unique_ptr<char[]> buf(new char[bufSize]);
|
||||
|
||||
// Relatively efficient way of reading from the decompressor (modulo
|
||||
// possible adjustments to 'bufSize', of course). The decompressed data is
|
||||
// first written to 'buf', then copied to 'roundTripResult'. There is no
|
||||
// other useless copy via, for instance, an intermediate std::string object,
|
||||
// as would be the case if we used std::string(buf.get(), bufSize).
|
||||
//
|
||||
// Of course, ideally 'roundTripResult' would directly pull from
|
||||
// 'decompressor' without going through 'buf', but I don't think this is
|
||||
// possible with std::stringstream and friends. Such an optimized data flow
|
||||
// is however straightforward to implement if you replace 'roundTripResult'
|
||||
// with a custom data sink that calls decompressor.read().
|
||||
do {
|
||||
decompressor.read(buf.get(), bufSize);
|
||||
if (decompressor.gcount() > 0) { // at least one char could be read
|
||||
roundTripResult.write(buf.get(), decompressor.gcount());
|
||||
}
|
||||
} while (decompressor && roundTripResult);
|
||||
|
||||
// 1) If set, badbit would have caused an exception to be raised (see above).
|
||||
// 2) failbit doesn't necessarily indicate an error here: it is set as soon
|
||||
// as the read() call can't provide the requested number of characters.
|
||||
SG_VERIFY(decompressor.eof() && !decompressor.bad());
|
||||
// Because of std::ostringstream::write(), 'roundTripResult' might have its
|
||||
// failbit or badbit set, either of which would indicate a real problem.
|
||||
SG_VERIFY(roundTripResult);
|
||||
SG_CHECK_EQUAL(roundTripResult.str(), someString);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
test_pipeCompOrDecompIStreambufIntoOStream();
|
||||
test_StreambufBasicOperations();
|
||||
test_ZlibDecompressorIStreambuf_readLargestPossibleAmount();
|
||||
test_RoundTripMultiWithIStreams();
|
||||
test_formattedInputFromDecompressor();
|
||||
test_ZlibDecompressorIStream_readPutbackEtc();
|
||||
test_IStreamConstructorWithSinkSemantics();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
$Id: netBuffer.cxx 1568 2002-09-02 06:05:49Z sjbaker $
|
||||
*/
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include "sg_netBuffer.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
// to write or something...]
|
||||
// Maybe assert valid handle, too?
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include "sg_netChannel.hxx"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
sglog().setLogLevels( SG_ALL, SG_DEBUG );
|
||||
|
||||
const char * EXISTING_RECORD = argc > 1 ? argv[1] : "terrasync.flightgear.org";
|
||||
const char * QSERVICE = argc > 2 ? argv[2] : "https+ws20";
|
||||
|
||||
Watchdog watchdog;
|
||||
watchdog.start(100);
|
||||
|
||||
@@ -98,8 +101,7 @@ int main(int argc, char* argv[])
|
||||
cout << "done" << endl;
|
||||
}
|
||||
|
||||
#define EXISTING_RECORD "terrasync.flightgear.org"
|
||||
cout << "test existing NAPTR: " EXISTING_RECORD << endl;
|
||||
cout << "test existing NAPTR: " << EXISTING_RECORD << endl;
|
||||
{
|
||||
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(EXISTING_RECORD);
|
||||
DNS::Request_ptr r(naptrRequest);
|
||||
@@ -110,23 +112,24 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
if( r->isTimeout() ) {
|
||||
cerr << "timeout testing existing record " EXISTING_RECORD << endl;
|
||||
cerr << "timeout testing existing record " << EXISTING_RECORD << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(naptrRequest->entries.empty()) {
|
||||
cerr << "no results for " EXISTING_RECORD << endl;
|
||||
cerr << "no results for " << EXISTING_RECORD << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
cout << "test for ascending preference/order" << endl;
|
||||
int order = -1, preference = -1;
|
||||
for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin(); it != naptrRequest->entries.end(); ++it ) {
|
||||
cout << "NAPTR " << (*it)->order << " " << (*it)->preference << " '" << (*it)->service << "' '" << (*it)->regexp << "' '" << (*it)->replacement << "'" << endl;
|
||||
// currently only support "U" which implies empty replacement
|
||||
SG_CHECK_EQUAL((*it)->flags, "U" );
|
||||
SG_CHECK_EQUAL(naptrRequest->entries[0]->replacement, "" );
|
||||
|
||||
// currently only support ws20
|
||||
SG_CHECK_EQUAL((*it)->service, "ws20" );
|
||||
// currently only support ws20, disable temporarily
|
||||
//SG_CHECK_EQUAL((*it)->service, "ws20" );
|
||||
|
||||
if( (*it)->order < order ) {
|
||||
cerr << "NAPTR entries not ascending for field 'order'" << endl;
|
||||
@@ -154,6 +157,29 @@ int main(int argc, char* argv[])
|
||||
|
||||
}
|
||||
}
|
||||
cout << "test existing NAPTR with explicit qservice: " << QSERVICE << endl;
|
||||
{
|
||||
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(EXISTING_RECORD);
|
||||
naptrRequest->qservice = QSERVICE;
|
||||
DNS::Request_ptr r(naptrRequest);
|
||||
cl.makeRequest(r);
|
||||
while( !r->isComplete() && !r->isTimeout()) {
|
||||
SGTimeStamp::sleepForMSec(200);
|
||||
cl.update(0);
|
||||
}
|
||||
|
||||
if( r->isTimeout() ) {
|
||||
cerr << "timeout testing existing record " << EXISTING_RECORD << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(naptrRequest->entries.empty()) {
|
||||
cerr << "no results for " << EXISTING_RECORD << endl;
|
||||
//return EXIT_FAILURE; // not yet a failure - probably add this for 2017.4 and create DNS entries
|
||||
}
|
||||
for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin(); it != naptrRequest->entries.end(); ++it ) {
|
||||
cout << "NAPTR " << (*it)->order << " " << (*it)->preference << " '" << (*it)->service << "' '" << (*it)->regexp << "' '" << (*it)->replacement << "'" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
cout << "test non-existing NAPTR" << endl;
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Test harness.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "untar.hxx"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -341,18 +341,16 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
simd4_t(void) : simd4(_mm_setzero_ps()) {}
|
||||
simd4_t(float f) {}
|
||||
simd4_t(void) { simd4 = _mm_setzero_ps(); }
|
||||
simd4_t(float f) { simd4 = _mm_set1_ps(f); }
|
||||
simd4_t(float x, float y) : simd4_t(x,y,0,0) {}
|
||||
simd4_t(float x, float y, float z) : simd4_t(x,y,z,0) {}
|
||||
simd4_t(float x, float y, float z, float w) {}
|
||||
simd4_t(const __vec4f_t v) {}
|
||||
simd4_t(const simd4_t<float,4>& v) {}
|
||||
simd4_t(const simd4_t<float,3>& v) {}
|
||||
simd4_t(const simd4_t<float,2>& v) {}
|
||||
simd4_t(const __m128& v) {
|
||||
simd4 = v;
|
||||
}
|
||||
simd4_t(float x, float y, float z, float w) { simd4 = _mm_set_ps(w,z,y,x); }
|
||||
simd4_t(const __vec4f_t v) { simd4 = _mm_loadu_ps(v); }
|
||||
simd4_t(const simd4_t<float,4>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const simd4_t<float,3>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const simd4_t<float,2>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const __m128& v) { simd4 = v; }
|
||||
|
||||
inline const __m128 (&v4(void) const) {
|
||||
return simd4;
|
||||
@@ -583,18 +581,18 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
simd4_t(void) : simd4(_mm256_setzero_pd()) {}
|
||||
simd4_t(double d) {}
|
||||
simd4_t(void) { simd4 = _mm256_setzero_pd(); }
|
||||
simd4_t(double d) { simd4 = _mm256_set1_pd(d); }
|
||||
simd4_t(double x, double y) : simd4_t(x,y,0,0) {}
|
||||
simd4_t(double x, double y, double z) : simd4_t(x,y,z,0) {}
|
||||
simd4_t(double x, double y, double z, double w) {}
|
||||
simd4_t(const __vec4d_t v) {}
|
||||
simd4_t(const simd4_t<double,4>& v) {}
|
||||
simd4_t(const simd4_t<double,3>& v) {}
|
||||
simd4_t(const simd4_t<double,2>& v) {}
|
||||
simd4_t(const __m256d& v) {
|
||||
simd4 = v;
|
||||
simd4_t(double x, double y, double z, double w) {
|
||||
simd4 = _mm256_set_pd(w,z,y,x);
|
||||
}
|
||||
simd4_t(const __vec4d_t v) { simd4 = _mm256_loadu_pd(v); }
|
||||
simd4_t(const simd4_t<double,4>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const simd4_t<double,3>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const simd4_t<double,2>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const __m256d& v) { simd4 = v; }
|
||||
|
||||
inline const __m256d (&v4(void) const) {
|
||||
return simd4;
|
||||
@@ -819,14 +817,24 @@ private:
|
||||
|
||||
public:
|
||||
simd4_t(void) { simd4[0] = simd4[1] = _mm_setzero_pd(); }
|
||||
simd4_t(double d) {}
|
||||
simd4_t(double d) { simd4[0] = simd4[1] = _mm_set1_pd(d); }
|
||||
simd4_t(double x, double y) : simd4_t(x,y,0,0) {}
|
||||
simd4_t(double x, double y, double z) : simd4_t(x,y,z,0) {}
|
||||
simd4_t(double x, double y, double z, double w) {}
|
||||
simd4_t(const __vec4d_t v) {}
|
||||
simd4_t(const simd4_t<double,4>& v) {}
|
||||
simd4_t(const simd4_t<double,3>& v) {}
|
||||
simd4_t(const simd4_t<double,2>& v) {}
|
||||
simd4_t(double x, double y, double z, double w) {
|
||||
simd4[0] = _mm_set_pd(y,x); simd4[1] = _mm_set_pd(w,z);
|
||||
}
|
||||
simd4_t(const __vec4d_t v) {
|
||||
simd4[0] = _mm_loadu_pd(v); simd4[1] = _mm_loadu_pd(v+2);
|
||||
}
|
||||
simd4_t(const simd4_t<double,4>& v) {
|
||||
simd4[0] = v.v4()[0]; simd4[1] = v.v4()[1];
|
||||
}
|
||||
simd4_t(const simd4_t<double,3>& v) {
|
||||
simd4[0] = v.v4()[0]; simd4[1] = v.v4()[1];
|
||||
}
|
||||
simd4_t(const simd4_t<double,2>& v) {
|
||||
simd4[0] = v.v4()[0]; simd4[1] = _mm_setzero_pd();
|
||||
}
|
||||
simd4_t(const __m128d v[2]) {
|
||||
simd4[0] = v[0];
|
||||
simd4[1] = v[1];
|
||||
@@ -1101,18 +1109,16 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
simd4_t(void) : simd4(_mm_setzero_si128()) {}
|
||||
simd4_t(int i) {}
|
||||
simd4_t(void) { simd4 = _mm_setzero_si128(); }
|
||||
simd4_t(int i) { simd4 = _mm_set1_epi32(i); }
|
||||
simd4_t(int x, int y) : simd4_t(x,y,0,0) {}
|
||||
simd4_t(int x, int y, int z) : simd4_t(x,y,z,0) {}
|
||||
simd4_t(int x, int y, int z, int w) {}
|
||||
simd4_t(const __vec4i_t v) {}
|
||||
simd4_t(const simd4_t<int,4>& v) {}
|
||||
simd4_t(const simd4_t<int,3>& v) {}
|
||||
simd4_t(const simd4_t<int,2>& v) {}
|
||||
simd4_t(const __m128i& v) {
|
||||
simd4 = v;
|
||||
}
|
||||
simd4_t(int x, int y, int z, int w) { simd4 = _mm_set_epi32(w,z,y,x); }
|
||||
simd4_t(const __vec4i_t v) { simd4 = _mm_loadu_si128((const __m128i*)v); }
|
||||
simd4_t(const simd4_t<int,4>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const simd4_t<int,3>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const simd4_t<int,2>& v) { simd4 = v.v4(); }
|
||||
simd4_t(const __m128i& v) { simd4 = v; }
|
||||
|
||||
inline __m128i (&v4(void)) {
|
||||
return simd4;
|
||||
|
||||
@@ -7,6 +7,7 @@ set(HEADERS
|
||||
ResourceManager.hxx
|
||||
SimpleMarkdown.hxx
|
||||
SVGpreserveAspectRatio.hxx
|
||||
argparse.hxx
|
||||
interpolator.hxx
|
||||
make_new.hxx
|
||||
sg_dir.hxx
|
||||
@@ -17,6 +18,7 @@ set(HEADERS
|
||||
strutils.hxx
|
||||
tabbed_values.hxx
|
||||
texcoord.hxx
|
||||
test_macros.hxx
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
@@ -24,6 +26,7 @@ set(SOURCES
|
||||
ResourceManager.cxx
|
||||
SimpleMarkdown.cxx
|
||||
SVGpreserveAspectRatio.cxx
|
||||
argparse.cxx
|
||||
interpolator.cxx
|
||||
sg_dir.cxx
|
||||
sg_path.cxx
|
||||
@@ -45,6 +48,10 @@ simgear_component(misc misc "${SOURCES}" "${HEADERS}")
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
|
||||
add_executable(test_argparse argparse_test.cxx)
|
||||
target_link_libraries(test_argparse ${TEST_LIBS})
|
||||
add_test(argparse ${EXECUTABLE_OUTPUT_PATH}/test_argparse)
|
||||
|
||||
add_executable(test_CSSBorder CSSBorder_test.cxx)
|
||||
add_test(CSSBorder ${EXECUTABLE_OUTPUT_PATH}/test_CSSBorder)
|
||||
target_link_libraries(test_CSSBorder ${TEST_LIBS})
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// 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 <simgear_config.h>
|
||||
|
||||
#include "SVGpreserveAspectRatio.hxx"
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
391
simgear/misc/argparse.cxx
Normal file
391
simgear/misc/argparse.cxx
Normal file
@@ -0,0 +1,391 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// argparse.cxx --- Simple, generic parser for command-line arguments
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility> // std::pair, std::move()
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstring> // std::strcmp()
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include "argparse.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::shared_ptr;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace argparse
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * Base class for custom exceptions *
|
||||
// ***************************************************************************
|
||||
Error::Error(const string& message, const std::string& origin)
|
||||
: sg_exception("Argument parser error: " + message, origin)
|
||||
{ }
|
||||
|
||||
Error::Error(const char* message, const char* origin)
|
||||
: Error(string(message), string(origin))
|
||||
{ }
|
||||
|
||||
// ***************************************************************************
|
||||
// * OptionDesc class *
|
||||
// ***************************************************************************
|
||||
OptionDesc::OptionDesc(
|
||||
const string& optionId, std::vector<char> shortAliases,
|
||||
std::vector<string> longAliases, OptionArgType argumentType)
|
||||
: _id(optionId),
|
||||
_shortAliases(shortAliases),
|
||||
_longAliases(longAliases),
|
||||
_argumentType(argumentType)
|
||||
{ }
|
||||
|
||||
const std::string& OptionDesc::id() const
|
||||
{ return _id; }
|
||||
|
||||
const std::vector<char>& OptionDesc::shortAliases() const
|
||||
{ return _shortAliases; }
|
||||
|
||||
const std::vector<std::string>& OptionDesc::longAliases() const
|
||||
{ return _longAliases; }
|
||||
|
||||
OptionArgType OptionDesc::argumentType() const
|
||||
{ return _argumentType; }
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// * OptionValue class *
|
||||
// ***************************************************************************
|
||||
OptionValue::OptionValue(shared_ptr<const OptionDesc> optionDesc,
|
||||
const string& passedAs, const string& value,
|
||||
bool hasValue)
|
||||
: _optionDesc(std::move(optionDesc)),
|
||||
_passedAs(passedAs),
|
||||
_value(value),
|
||||
_hasValue(hasValue)
|
||||
{ }
|
||||
|
||||
shared_ptr<const OptionDesc> OptionValue::optionDesc() const
|
||||
{ return _optionDesc; } // return a copy of the shared_ptr
|
||||
|
||||
void OptionValue::setOptionDesc(shared_ptr<const OptionDesc> descPtr)
|
||||
{ _optionDesc = std::move(descPtr); }
|
||||
|
||||
string OptionValue::passedAs() const
|
||||
{ return _passedAs; }
|
||||
|
||||
void OptionValue::setPassedAs(const string& passedAs)
|
||||
{ _passedAs = passedAs; }
|
||||
|
||||
string OptionValue::value() const
|
||||
{ return _value; }
|
||||
|
||||
void OptionValue::setValue(const string& value)
|
||||
{ _value = value; }
|
||||
|
||||
bool OptionValue::hasValue() const
|
||||
{ return _hasValue; }
|
||||
|
||||
void OptionValue::setHasValue(bool hasValue)
|
||||
{ _hasValue = hasValue; }
|
||||
|
||||
const string OptionValue::id() const
|
||||
{
|
||||
const auto desc = optionDesc();
|
||||
return (desc) ? desc->id() : string();
|
||||
}
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// * ArgumentParser class *
|
||||
// ***************************************************************************
|
||||
|
||||
// Static utility method.
|
||||
std::vector<char>
|
||||
ArgumentParser::removeHyphens(const std::vector<string>& shortAliases,
|
||||
std::vector<string>& longAliases)
|
||||
{
|
||||
std::vector<char> shortAliasesCharVec;
|
||||
shortAliasesCharVec.reserve(shortAliases.size());
|
||||
|
||||
for (const string& opt: shortAliases) {
|
||||
if (opt.size() != 2 || opt[0] != '-' || opt[1] == '-' || opt[1] > 127) {
|
||||
throw Error("unexpected form for a short option: '" + opt + "' (expecting "
|
||||
"a string of size 2 whose first character is a hyphen and "
|
||||
"second character an ASCII char that is not a hyphen)");
|
||||
}
|
||||
|
||||
shortAliasesCharVec.emplace_back(opt[1]); // emplace the char after hyphen
|
||||
}
|
||||
|
||||
for (string& longOpt: longAliases) {
|
||||
if (longOpt.size() < 3 ||
|
||||
!simgear::strutils::starts_with(longOpt, string("--"))) {
|
||||
throw Error("unexpected form for a long option: '" + longOpt + "' "
|
||||
"(expecting a string of size 3 or more that starts with "
|
||||
"two hyphens)");
|
||||
}
|
||||
|
||||
longOpt.erase(0, 2); // remove the two leading hyphens
|
||||
}
|
||||
|
||||
return shortAliasesCharVec;
|
||||
}
|
||||
|
||||
void
|
||||
ArgumentParser::addOption(const string& optionId,
|
||||
OptionArgType argType,
|
||||
std::vector<string> shortAliases,
|
||||
std::vector<string> longAliases)
|
||||
{
|
||||
// Remove the leading dashes and do a sanity check for these arguments
|
||||
std::vector<char> shortAliasesCharVec = removeHyphens(shortAliases,
|
||||
longAliases);
|
||||
|
||||
const auto desc_p = std::make_shared<const OptionDesc>(
|
||||
optionId, std::move(shortAliasesCharVec), std::move(longAliases), argType);
|
||||
|
||||
for (const char c: desc_p->shortAliases()) {
|
||||
if (!_shortOptionMap.emplace(c, desc_p).second) {
|
||||
throw Error(
|
||||
"trying to add option '-" + string(1, c) + "', however it is already "
|
||||
"in the short option map");
|
||||
}
|
||||
}
|
||||
|
||||
for (const string& longOpt: desc_p->longAliases()) {
|
||||
if (!_longOptionMap.emplace(longOpt, desc_p).second) {
|
||||
throw Error(
|
||||
"trying to add option '--" + longOpt + "', however it is already in "
|
||||
"the long option map");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgumentParser::addOption(const string& optionId, OptionArgType argumentType,
|
||||
string shortOpt, string longOpt)
|
||||
{
|
||||
std::vector<string> shortOptList;
|
||||
std::vector<string> longOptList;
|
||||
|
||||
if (!shortOpt.empty()) {
|
||||
shortOptList.push_back(std::move(shortOpt));
|
||||
}
|
||||
|
||||
if (!longOpt.empty()) {
|
||||
longOptList.push_back(std::move(longOpt));
|
||||
}
|
||||
|
||||
addOption(optionId, argumentType, std::move(shortOptList),
|
||||
std::move(longOptList));
|
||||
}
|
||||
|
||||
std::pair< std::vector<OptionValue>, std::vector<string> >
|
||||
ArgumentParser::parseArgs(int argc, const char *const *argv) const
|
||||
{
|
||||
std::pair< std::vector<OptionValue>, std::vector<string> > res;
|
||||
std::vector<OptionValue>& optsWithValues = res.first;
|
||||
std::vector<string>& nonOptionArgs = res.second;
|
||||
bool inOptions = true;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
// Decode from command line encoding
|
||||
const string currentArg = cmdEncToUtf8(argv[i]);
|
||||
|
||||
if ((inOptions) && (currentArg == "--")) {
|
||||
// We found the end-of-options delimiter
|
||||
inOptions = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inOptions) {
|
||||
if (currentArg.size() >= 2 && currentArg[0] == '-') {
|
||||
if (currentArg[1] == '-') {
|
||||
i += readLongOption(argc, argv, currentArg, i+1, optsWithValues);
|
||||
} else {
|
||||
i += readShortOptions(argc, argv, currentArg, i+1, optsWithValues);
|
||||
}
|
||||
} else {
|
||||
// The argument is neither an option, nor a cluster of short options.
|
||||
inOptions = false;
|
||||
nonOptionArgs.push_back(currentArg);
|
||||
}
|
||||
} else {
|
||||
nonOptionArgs.push_back(currentArg);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Static method
|
||||
string ArgumentParser::cmdEncToUtf8(const string& s)
|
||||
{
|
||||
#if defined(SG_WINDOWS)
|
||||
// Untested code path. Comments and/or testing by Windows people welcome.
|
||||
return simgear::strutils::convertWindowsLocal8BitToUtf8(s);
|
||||
#else
|
||||
// XXX This assumes UTF-8 encoding for command line arguments on non-Windows
|
||||
// platforms. Unfortunately, the current (April 2017) standard C++ API for
|
||||
// encoding conversions has big problems (cf.
|
||||
// <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0618r0.html>).
|
||||
// Should be fixed when we have a good way to do such conversions.
|
||||
return s;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return the number of arguments used by the option value, if any (i.e., how
|
||||
// much the caller should shift to resume arguments processing).
|
||||
int ArgumentParser::readLongOption(int argc, const char *const *argv,
|
||||
const string& currentArg, int nextArgIdx,
|
||||
std::vector<OptionValue>& optsWithValues)
|
||||
const
|
||||
{
|
||||
const string s = currentArg.substr(2); // skip the two initial dashes
|
||||
|
||||
// UTF-8 guarantees that ASCII bytes (here, '=') cannot be part of the
|
||||
// encoding of a non-ASCII character.
|
||||
std::size_t optEnd = s.find('=');
|
||||
string opt = s.substr(0, optEnd);
|
||||
|
||||
const auto mapElt = _longOptionMap.find(opt);
|
||||
if (mapElt != _longOptionMap.end()) {
|
||||
const shared_ptr<const OptionDesc>& optDesc = mapElt->second;
|
||||
OptionValue optVal(optDesc, string("--") + opt);
|
||||
|
||||
switch (optDesc->argumentType()) {
|
||||
case OptionArgType::NO_ARGUMENT:
|
||||
optVal.setHasValue(false);
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 0;
|
||||
case OptionArgType::OPTIONAL_ARGUMENT: // pass through
|
||||
case OptionArgType::MANDATORY_ARGUMENT:
|
||||
if (optEnd != string::npos) {
|
||||
// The optional value is present in the same command line
|
||||
// argument as the option name (syntax '--option=value').
|
||||
optVal.setHasValue(true);
|
||||
optVal.setValue(s.substr(optEnd + 1));
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 0;
|
||||
} else if (nextArgIdx < argc &&
|
||||
(argv[nextArgIdx][0] != '-' ||
|
||||
!std::strcmp(argv[nextArgIdx], "-"))) {
|
||||
// The optional value is present as a separate command line argument
|
||||
// (syntax '--option value').
|
||||
optVal.setHasValue(true);
|
||||
optVal.setValue(cmdEncToUtf8(argv[nextArgIdx]));
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 1;
|
||||
} else if (optDesc->argumentType() ==
|
||||
OptionArgType::OPTIONAL_ARGUMENT) {
|
||||
// No argument (value) can be found for the option
|
||||
optVal.setHasValue(false);
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 0;
|
||||
} else {
|
||||
assert(optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
|
||||
throw InvalidUserInput("option '" + optVal.passedAs() + "' requires an "
|
||||
"argument, but none was provided");
|
||||
}
|
||||
default:
|
||||
throw sg_error("This piece of code should be unreachable.");
|
||||
}
|
||||
} else {
|
||||
throw InvalidUserInput("invalid option: '--" + opt + "'");
|
||||
}
|
||||
}
|
||||
|
||||
int ArgumentParser::readShortOptions(int argc, const char *const *argv,
|
||||
const string& currentArg, int nextArgIdx,
|
||||
std::vector<OptionValue>& optsWithValues)
|
||||
const
|
||||
{
|
||||
shared_ptr<const OptionDesc> optDesc;
|
||||
const string s = currentArg.substr(1); // skip the initial dash
|
||||
std::size_t i = 0; // index inside s
|
||||
|
||||
// Read all options taking no argument in 'currentArg'; stop at the first
|
||||
// taking an optional or mandatory argument.
|
||||
for (/* empty */; i < s.size(); i++) {
|
||||
const auto mapElt = _shortOptionMap.find(s[i]);
|
||||
if (mapElt != _shortOptionMap.end()) {
|
||||
optDesc = mapElt->second;
|
||||
|
||||
if (optDesc->argumentType() == OptionArgType::NO_ARGUMENT) {
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i], string(),
|
||||
false /* no value */);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw InvalidUserInput(string("invalid option: '-") + s[i] + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (i == s.size()) {
|
||||
// The command line argument in 'currentArg' was fully read and only
|
||||
// contains options that take no argument.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We've already “eaten” all options taking no argument in 'currentArg'.
|
||||
assert(optDesc->argumentType() == OptionArgType::OPTIONAL_ARGUMENT ||
|
||||
optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
|
||||
|
||||
if (i + 1 < s.size()) {
|
||||
// The option has a value at the end of 'currentArg': s.substr(i+1)
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i], s.substr(i+1),
|
||||
true /* hasValue */);
|
||||
return 0;
|
||||
} else if (nextArgIdx < argc &&
|
||||
(argv[nextArgIdx][0] != '-' ||
|
||||
!std::strcmp(argv[nextArgIdx], "-"))) {
|
||||
assert(i + 1 == s.size());
|
||||
// The option is at the end of 'currentArg' and has a value:
|
||||
// argv[nextArgIdx].
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i],
|
||||
cmdEncToUtf8(argv[nextArgIdx]),
|
||||
true /* hasValue */);
|
||||
return 1;
|
||||
} else if (optDesc->argumentType() ==
|
||||
OptionArgType::OPTIONAL_ARGUMENT) {
|
||||
// No argument (value) can be found for the option
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i], string(),
|
||||
false /* no value */);
|
||||
return 0;
|
||||
} else {
|
||||
assert(optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
|
||||
throw InvalidUserInput(string("option '-") + s[i] + "' requires an "
|
||||
"argument, but none was provided");
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace argparse
|
||||
|
||||
} // of namespace simgear
|
||||
281
simgear/misc/argparse.hxx
Normal file
281
simgear/misc/argparse.hxx
Normal file
@@ -0,0 +1,281 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// argparse.hxx --- Simple, generic parser for command-line arguments
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef _SIMGEAR_ARGPARSE_HXX_
|
||||
#define _SIMGEAR_ARGPARSE_HXX_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <utility> // std::pair
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
// Usage example:
|
||||
//
|
||||
// using simgear::argparse::OptionArgType;
|
||||
//
|
||||
// simgear::argparse::ArgumentParser parser;
|
||||
// parser.addOption("root option", OptionArgType::MANDATORY_ARGUMENT,
|
||||
// "", "--root");
|
||||
// parser.addOption("test option", OptionArgType::NO_ARGUMENT, "-t", "--test");
|
||||
//
|
||||
// const auto res = parser.parseArgs(argc, argv);
|
||||
//
|
||||
// for (const auto& opt: res.first) {
|
||||
// std::cerr << "Got option '" << opt.id() << "' as '" << opt.passedAs() <<
|
||||
// "'" << ((opt.hasValue()) ? " with value '" + opt.value() + "'" : "") <<
|
||||
// "\n";
|
||||
// }
|
||||
//
|
||||
// for (const auto& arg: res.second) {
|
||||
// std::cerr << "Got non-option argument '" << arg << "'\n";
|
||||
// }
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace argparse
|
||||
{
|
||||
|
||||
// Custom exception classes
|
||||
class Error : public sg_exception
|
||||
{
|
||||
public:
|
||||
explicit Error(const std::string& message,
|
||||
const std::string& origin = std::string());
|
||||
explicit Error(const char* message, const char* origin = nullptr);
|
||||
};
|
||||
|
||||
class InvalidUserInput : public Error
|
||||
{
|
||||
using Error::Error; // inherit all constructors
|
||||
};
|
||||
|
||||
|
||||
enum class OptionArgType {
|
||||
NO_ARGUMENT = 0,
|
||||
OPTIONAL_ARGUMENT,
|
||||
MANDATORY_ARGUMENT
|
||||
};
|
||||
|
||||
// All strings inside this class are encoded in UTF-8.
|
||||
class OptionDesc
|
||||
{
|
||||
public:
|
||||
explicit OptionDesc(const std::string& optionId,
|
||||
std::vector<char> shortAliases,
|
||||
std::vector<std::string> longAliases,
|
||||
OptionArgType argumentType);
|
||||
|
||||
// Simple getters for the private members
|
||||
const std::string& id() const;
|
||||
const std::vector<char>& shortAliases() const;
|
||||
const std::vector<std::string>& longAliases() const;
|
||||
OptionArgType argumentType() const;
|
||||
|
||||
private:
|
||||
// Option identifier, invisible to the end user. Used to easily refer to the
|
||||
// option despite the various forms it may take (short and/or long aliases).
|
||||
std::string _id;
|
||||
// Each element of _shortAliases must be an ASCII character. For instance,
|
||||
// 'o' for an option called '-o'.
|
||||
std::vector<char> _shortAliases;
|
||||
// Each element of _longAliases should be the name of a long option, with
|
||||
// the two leading dashes removed. For instance, 'generate-foobar' for an
|
||||
// option named '--generate-foobar'.
|
||||
std::vector<std::string> _longAliases;
|
||||
OptionArgType _argumentType;
|
||||
};
|
||||
|
||||
// All strings inside this class are encoded in UTF-8.
|
||||
class OptionValue
|
||||
{
|
||||
public:
|
||||
explicit OptionValue(std::shared_ptr<const OptionDesc> optionDesc,
|
||||
const std::string& passedAs,
|
||||
const std::string& value = std::string(),
|
||||
bool hasValue = false);
|
||||
|
||||
// Simple getters/accessors for the private members
|
||||
std::shared_ptr<const OptionDesc> optionDesc() const;
|
||||
std::string passedAs() const;
|
||||
std::string value() const;
|
||||
bool hasValue() const;
|
||||
|
||||
// The corresponding setters
|
||||
void setOptionDesc(std::shared_ptr<const OptionDesc>);
|
||||
void setPassedAs(const std::string&);
|
||||
void setValue(const std::string&);
|
||||
void setHasValue(bool);
|
||||
|
||||
// For convenience: get the option ID from the result of optionDesc()
|
||||
const std::string id() const;
|
||||
|
||||
private:
|
||||
// Pointer to the option descriptor.
|
||||
std::shared_ptr<const OptionDesc> _optionDesc;
|
||||
// Exact option passed (e.g., -f or --foobar).
|
||||
std::string _passedAs;
|
||||
// Value given for the option, if any (otherwise, the empty string).
|
||||
std::string _value;
|
||||
// Tells whether the option has been given a value. This is of course mainly
|
||||
// useful for options taking an *optional* argument. The value in question
|
||||
// can be the empty string, if given on a separate command line argument
|
||||
// from the option.
|
||||
bool _hasValue;
|
||||
};
|
||||
|
||||
|
||||
// Main class for command line processing. Every string coming out of it is
|
||||
// encoded in UTF-8.
|
||||
class ArgumentParser
|
||||
{
|
||||
public:
|
||||
// Register an option, with zero or more short aliases (e.g., -a, -u, - F)
|
||||
// and zero or more long aliases (e.g., --foobar, --barnum, --bleh). The
|
||||
// option may take no argument, or one optional argument, or one mandatory
|
||||
// argument. The 'optionId' is used to refer to the option in a clear and
|
||||
// simple way, even in the presence of several short or long aliases. It is
|
||||
// thus visible to the programmer using this API, but not to users of the
|
||||
// command line interface being implemented.
|
||||
//
|
||||
// Note: this method and all its overloads take options in the form "-o" or
|
||||
// "--foobar" (as std::string instances). While it would be possible
|
||||
// to only require a char for each short option and to take long
|
||||
// option declarations without the two leading dashes, the API chosen
|
||||
// here should lead to more readable and searchable user code.
|
||||
//
|
||||
// shortAliases: each element should consist of two characters: an ASCII
|
||||
// hyphen (-) followed by an ASCII character.
|
||||
// longAliases: each element should be a string in UTF-8 encoding, starting
|
||||
// with two ASCII/UTF-8 hyphens (U+002D).
|
||||
//
|
||||
// This API could be extended to automatically generate --help output from
|
||||
// strings passed to addOption().
|
||||
void addOption(const std::string& optionId,
|
||||
OptionArgType argumentType,
|
||||
std::vector<std::string> shortAliases,
|
||||
std::vector<std::string> longAliases);
|
||||
// Convenience overload that should be enough for most cases. To register
|
||||
// only a short option or only a long option, simply pass the empty string
|
||||
// for the corresponding parameter.
|
||||
void addOption(const std::string& optionId,
|
||||
OptionArgType argumentType,
|
||||
std::string shortOpt = std::string(),
|
||||
std::string longOpt = std::string());
|
||||
|
||||
// Parse arguments from an argc/argv pair of variables. 'argc' should be the
|
||||
// number of elements in 'argv', the first of which is ignored for the sake
|
||||
// of options and arguments extraction (since it normally holds the program
|
||||
// name).
|
||||
//
|
||||
// Note: this “number of elements” doesn't count the usual---and completely
|
||||
// unneeded here---final null pointer.
|
||||
//
|
||||
// Short options may be grouped in the usual way. For instance, if '-x',
|
||||
// '-z' and '-f' are three short options, the first two taking no argument
|
||||
// and '-f' taking one mandatory argument, then both '-xzf bar' and
|
||||
// '-xzfbar' are equivalent to '-x -z -f bar' as well as to '-x -z -fbar'
|
||||
// ('bar' being the value taken by option '-f').
|
||||
//
|
||||
// Long options are handled in the usual way too:
|
||||
//
|
||||
// '--foobar' for an option taking no argument
|
||||
//
|
||||
// '--foobar=value' for an option taking an optional or mandatory
|
||||
// or '--foobar value' argument (two separate command line arguments in the
|
||||
// second case)
|
||||
//
|
||||
// Long option names may contain spaces, though this is extremely uncommon
|
||||
// and inconvenient for users. Any option argument (be it for a long or a
|
||||
// short option) may contain spaces, as expected.
|
||||
//
|
||||
// As usual too, the special '--' argument consisting of two ASCII/UTF-8
|
||||
// hyphens, can be used to cause all subsequent arguments to be treated as
|
||||
// non-option arguments, regardless of whether they start with a hyphen or
|
||||
// not. In the absence of this special argument, the first argument that is
|
||||
// not the value of an option and is either a single hyphen, or doesn't
|
||||
// start with a hyphen, marks the end of options. This and all subsequent
|
||||
// arguments are read as non-option arguments.
|
||||
//
|
||||
// Return a pair containing:
|
||||
// - the list of supplied options (with their respective values, when
|
||||
// applicable);
|
||||
// - the list of non-option arguments that were given after the options.
|
||||
//
|
||||
// Both of these lists (vectors) may be empty and preserve the order used in
|
||||
// 'argv'.
|
||||
std::pair< std::vector<OptionValue>, std::vector<std::string> >
|
||||
parseArgs(int argc, const char *const *argv) const;
|
||||
|
||||
private:
|
||||
// Convert from the encoding used for argv (command line arguments) to
|
||||
// UTF-8.
|
||||
//
|
||||
// This method is currently not very satisfactory (cf. comments in the
|
||||
// implementation). The Windows code path is untested; the non-Windows code
|
||||
// path assumes command line arguments are encoded in UTF-8 (in other words,
|
||||
// it's a no-op).
|
||||
static std::string cmdEncToUtf8(const std::string& stringInCmdLineEncoding);
|
||||
|
||||
// Remove leading dashes and do sanity checks. 'longAliases' is modified
|
||||
// in-place. 'shortAliases' is not, because we build an std::vector<char>
|
||||
// from an std::vector<std::string>.
|
||||
static std::vector<char> removeHyphens(
|
||||
const std::vector<std::string>& shortAliases,
|
||||
std::vector<std::string>& longAliases);
|
||||
|
||||
// Read a long option and its value, if any (in total: one or two command
|
||||
// line arguments).
|
||||
//
|
||||
// Return the number of arguments consumed by this process after
|
||||
// 'currentArg' (i.e., 0 or 1 depending on whether the last option in
|
||||
// 'currentArg' has been given a value).
|
||||
//
|
||||
// 'currentArg' comes from argv[nextArgIdx-1], after decoding by
|
||||
// cmdEncToUtf8(). Thus, argv[nextArgIdx] is the command-line argument
|
||||
// coming after 'currentArg'.
|
||||
int readLongOption(
|
||||
int argc, const char *const *argv, const std::string& currentArg,
|
||||
int nextArgIdx, std::vector<OptionValue>& optsWithValues) const;
|
||||
// Read all short options in a command line argument, plus the option value
|
||||
// of the last one of these, if any (even if the option value is in the next
|
||||
// command line argument).
|
||||
//
|
||||
// See readLongOption() for the return value and meaning of parameters.
|
||||
int readShortOptions(
|
||||
int argc, const char *const *argv, const std::string& currentArg,
|
||||
int nextArgIdx, std::vector<OptionValue>& optsWithValues) const;
|
||||
|
||||
// Keys are short option names without the leading dash
|
||||
std::unordered_map< char,
|
||||
std::shared_ptr<const OptionDesc> > _shortOptionMap;
|
||||
// Keys are long option names without the two leading dashes
|
||||
std::unordered_map< std::string,
|
||||
std::shared_ptr<const OptionDesc> > _longOptionMap;
|
||||
};
|
||||
|
||||
} // of namespace argparse
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // _SIMGEAR_ARGPARSE_HXX_
|
||||
621
simgear/misc/argparse_test.cxx
Normal file
621
simgear/misc/argparse_test.cxx
Normal file
@@ -0,0 +1,621 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// argparse_test.cxx --- Automated tests for argparse.cxx / argparse.hxx
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// 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 Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <iostream> // std::cout
|
||||
#include <vector>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include "argparse.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
void test_mixOfShortAndLongOptions()
|
||||
{
|
||||
cout << "Testing a mix of short and long options, plus non-option arguments"
|
||||
<< endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("test option", OptionArgType::NO_ARGUMENT, "-t");
|
||||
parser.addOption("long opt w/o arg", OptionArgType::NO_ARGUMENT,
|
||||
"", "--long-option-without-arg");
|
||||
parser.addOption("other test option", OptionArgType::NO_ARGUMENT, "-O");
|
||||
parser.addOption("yet another test option",
|
||||
OptionArgType::OPTIONAL_ARGUMENT, "-y", "--yes-we-can");
|
||||
parser.addOption("and again", OptionArgType::MANDATORY_ARGUMENT, "-a",
|
||||
"--all-you-need-is-love");
|
||||
parser.addOption("long option with opt arg",
|
||||
OptionArgType::OPTIONAL_ARGUMENT, "", // no short alias
|
||||
"--long-option-with-opt-arg");
|
||||
|
||||
// Using an std::vector to avoid the need to count the elements ourselves
|
||||
const vector<const char*> v({
|
||||
"FoobarProg", "-Oy", "-aarg for -a", "-OtOty", "arg for -y",
|
||||
"-tyOther arg for -y", "--long-option-without-arg", "-a", "Arg for -a",
|
||||
"--long-option-with-opt-arg", "--long-option-with-opt-arg", "value 1",
|
||||
"--long-option-with-opt-arg=value 2", "-t",
|
||||
"--all-you-need-is-love", "oh this is true", "-ypouet",
|
||||
"--all-you-need-is-love=right, I'll shut up ;-)", "non option",
|
||||
" other non option ", "--", "<-- came too late, treated as an arg"});
|
||||
// v.size() corresponds to argc, &v[0] corresponds to argv.
|
||||
const auto res = parser.parseArgs(v.size(), &v[0]);
|
||||
const auto& opts = res.first;
|
||||
const auto& otherArgs = res.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts.size(), 19); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs.size(), 4); // number of non-option arguments
|
||||
|
||||
// Check all passed options and their values
|
||||
SG_CHECK_EQUAL(opts[0].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[0].value(), "");
|
||||
SG_CHECK_EQUAL(opts[0].hasValue(), false);
|
||||
|
||||
SG_CHECK_EQUAL(opts[0].id(), "other test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->argumentType(),
|
||||
OptionArgType::NO_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'O'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->longAliases(), vector<string>());
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[1].value(), "");
|
||||
SG_CHECK_EQUAL(opts[1].hasValue(), false);
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].id(), "yet another test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->argumentType(),
|
||||
OptionArgType::OPTIONAL_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'y'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->longAliases(),
|
||||
vector<string>(1, "yes-we-can"));
|
||||
|
||||
SG_CHECK_EQUAL(opts[2].passedAs(), "-a");
|
||||
SG_CHECK_EQUAL(opts[2].value(), "arg for -a");
|
||||
SG_CHECK_EQUAL(opts[2].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[2].id(), "and again");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->argumentType(),
|
||||
OptionArgType::MANDATORY_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'a'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->longAliases(),
|
||||
vector<string>(1, "all-you-need-is-love"));
|
||||
|
||||
SG_CHECK_EQUAL(opts[3].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[3].value(), "");
|
||||
SG_CHECK_EQUAL(opts[3].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[3].id(), "other test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->argumentType(),
|
||||
OptionArgType::NO_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'O'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->longAliases(), vector<string>());
|
||||
|
||||
SG_CHECK_EQUAL(opts[4].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[4].value(), "");
|
||||
SG_CHECK_EQUAL(opts[4].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[4].id(), "test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->argumentType(),
|
||||
OptionArgType::NO_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 't'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->longAliases(), vector<string>());
|
||||
|
||||
SG_CHECK_EQUAL(opts[5].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[5].value(), "");
|
||||
SG_CHECK_EQUAL(opts[5].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[5].id(), "other test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[6].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[6].value(), "");
|
||||
SG_CHECK_EQUAL(opts[6].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[6].id(), "test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[7].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[7].value(), "arg for -y");
|
||||
SG_CHECK_EQUAL(opts[7].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[7].id(), "yet another test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[8].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[8].value(), "");
|
||||
SG_CHECK_EQUAL(opts[8].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[8].id(), "test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[9].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[9].value(), "Other arg for -y");
|
||||
SG_CHECK_EQUAL(opts[9].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[9].id(), "yet another test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[10].passedAs(), "--long-option-without-arg");
|
||||
SG_CHECK_EQUAL(opts[10].value(), "");
|
||||
SG_CHECK_EQUAL(opts[10].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[10].id(), "long opt w/o arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[11].passedAs(), "-a");
|
||||
SG_CHECK_EQUAL(opts[11].value(), "Arg for -a");
|
||||
SG_CHECK_EQUAL(opts[11].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[11].id(), "and again");
|
||||
|
||||
SG_CHECK_EQUAL(opts[12].passedAs(), "--long-option-with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[12].value(), "");
|
||||
SG_CHECK_EQUAL(opts[12].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[12].id(), "long option with opt arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[13].passedAs(), "--long-option-with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[13].value(), "value 1");
|
||||
SG_CHECK_EQUAL(opts[13].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[13].id(), "long option with opt arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[14].passedAs(), "--long-option-with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[14].value(), "value 2");
|
||||
SG_CHECK_EQUAL(opts[14].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[14].id(), "long option with opt arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[15].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[15].value(), "");
|
||||
SG_CHECK_EQUAL(opts[15].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[15].id(), "test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[16].passedAs(), "--all-you-need-is-love");
|
||||
SG_CHECK_EQUAL(opts[16].value(), "oh this is true");
|
||||
SG_CHECK_EQUAL(opts[16].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[16].id(), "and again");
|
||||
|
||||
SG_CHECK_EQUAL(opts[17].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[17].value(), "pouet");
|
||||
SG_CHECK_EQUAL(opts[17].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[17].id(), "yet another test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[18].passedAs(), "--all-you-need-is-love");
|
||||
SG_CHECK_EQUAL(opts[18].value(), "right, I'll shut up ;-)");
|
||||
SG_CHECK_EQUAL(opts[18].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[18].id(), "and again");
|
||||
|
||||
// Check all non-option arguments that were passed to parser.parseArgs()
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs,
|
||||
vector<string>({"non option", " other non option ", "--",
|
||||
"<-- came too late, treated as an arg"}));
|
||||
}
|
||||
|
||||
void test_whenOptionValueIsASingleHyphen()
|
||||
{
|
||||
cout << "Testing cases where a single hyphen is used as an option value" <<
|
||||
endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("option -T", OptionArgType::NO_ARGUMENT, "-T", "--test");
|
||||
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT,
|
||||
"-o", "--with-opt-arg");
|
||||
parser.addOption("option -m", OptionArgType::MANDATORY_ARGUMENT, "-m",
|
||||
"--with-mandatory-arg");
|
||||
|
||||
const vector<const char*> v({
|
||||
"FoobarProg", "-To", "-", "-o-", "-oT", "-o", "-T", "-o", "-",
|
||||
"--with-opt-arg=-", "--with-opt-arg", "-", "--with-opt-arg",
|
||||
"-m-", "--with-mandatory-arg=-", "--with-mandatory-arg", "-", "-m", "-",
|
||||
"non option 1", "non option 2", "non option 3"});
|
||||
const auto res = parser.parseArgs(v.size(), &v[0]);
|
||||
const auto& opts = res.first;
|
||||
const auto& otherArgs = res.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts.size(), 14); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs.size(), 3); // number of non-option arguments
|
||||
|
||||
SG_CHECK_EQUAL(opts[0].passedAs(), "-T");
|
||||
SG_CHECK_EQUAL(opts[0].value(), "");
|
||||
SG_CHECK_EQUAL(opts[0].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[0].id(), "option -T");
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[1].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[1].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[1].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[2].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[2].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[2].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[2].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[3].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[3].value(), "T");
|
||||
SG_CHECK_EQUAL(opts[3].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[3].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[4].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[4].value(), "");
|
||||
SG_CHECK_EQUAL(opts[4].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[4].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[5].passedAs(), "-T");
|
||||
SG_CHECK_EQUAL(opts[5].value(), "");
|
||||
SG_CHECK_EQUAL(opts[5].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[5].id(), "option -T");
|
||||
|
||||
SG_CHECK_EQUAL(opts[6].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[6].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[6].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[6].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[7].passedAs(), "--with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[7].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[7].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[7].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[8].passedAs(), "--with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[8].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[8].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[8].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[9].passedAs(), "--with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[9].value(), "");
|
||||
SG_CHECK_EQUAL(opts[9].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[9].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[10].passedAs(), "-m");
|
||||
SG_CHECK_EQUAL(opts[10].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[10].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[10].id(), "option -m");
|
||||
|
||||
SG_CHECK_EQUAL(opts[11].passedAs(), "--with-mandatory-arg");
|
||||
SG_CHECK_EQUAL(opts[11].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[11].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[11].id(), "option -m");
|
||||
|
||||
SG_CHECK_EQUAL(opts[12].passedAs(), "--with-mandatory-arg");
|
||||
SG_CHECK_EQUAL(opts[12].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[12].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[12].id(), "option -m");
|
||||
|
||||
SG_CHECK_EQUAL(opts[13].passedAs(), "-m");
|
||||
SG_CHECK_EQUAL(opts[13].value(), "-");
|
||||
SG_CHECK_EQUAL(opts[13].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[13].id(), "option -m");
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs,
|
||||
vector<string>({"non option 1", "non option 2", "non option 3"}));
|
||||
}
|
||||
|
||||
void test_frontierBetweenOptionsAndNonOptions()
|
||||
{
|
||||
cout << "Testing around the frontier between options and non-options" << endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("option -T", OptionArgType::NO_ARGUMENT, "-T");
|
||||
parser.addOption("long opt w/o arg", OptionArgType::NO_ARGUMENT,
|
||||
"", "--long-option-without-arg");
|
||||
parser.addOption("option -a", OptionArgType::MANDATORY_ARGUMENT, "-a",
|
||||
"--this-is-option-a");
|
||||
|
||||
// Test 1: both options and non-options; '--' used as a normal non-option
|
||||
// argument (i.e., after other non-option arguments).
|
||||
const vector<const char*> v1({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "non option 1",
|
||||
"non option 2", "--", "non option 3"});
|
||||
// v1.size() corresponds to argc, &v1[0] corresponds to argv.
|
||||
const auto res1 = parser.parseArgs(v1.size(), &v1[0]);
|
||||
const auto& opts1 = res1.first;
|
||||
const auto& otherArgs1 = res1.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts1.size(), 2); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs1.size(), 4); // number of non-option arguments
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs1,
|
||||
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 2: some options but no non-options arguments
|
||||
const vector<const char*> v2({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval"});
|
||||
const auto res2 = parser.parseArgs(v2.size(), &v2[0]);
|
||||
const auto& opts2 = res2.first;
|
||||
const auto& otherArgs2 = res2.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts2.size(), 2);
|
||||
SG_VERIFY(otherArgs2.empty());
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(otherArgs2, vector<string>());
|
||||
|
||||
// Test 3: same as test 2, but with useless end-of-options delimiter
|
||||
const vector<const char*> v3({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "--"});
|
||||
const auto res3 = parser.parseArgs(v3.size(), &v3[0]);
|
||||
const auto& opts3 = res3.first;
|
||||
const auto& otherArgs3 = res3.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts3.size(), 2);
|
||||
SG_VERIFY(otherArgs3.empty());
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(otherArgs3, vector<string>());
|
||||
|
||||
// Test 4: only non-option arguments
|
||||
const vector<const char*> v4({
|
||||
"FoobarProg", "non option 1",
|
||||
"non option 2", "--", "non option 3"});
|
||||
const auto res4 = parser.parseArgs(v4.size(), &v4[0]);
|
||||
const auto& opts4 = res4.first;
|
||||
const auto& otherArgs4 = res4.second;
|
||||
|
||||
SG_VERIFY(opts4.empty());
|
||||
SG_CHECK_EQUAL(otherArgs4.size(), 4);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs4,
|
||||
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 5: only non-options arguments, but starting with --
|
||||
const vector<const char*> v5({
|
||||
"FoobarProg", "--", "non option 1",
|
||||
"non option 2", "--", "non option 3"});
|
||||
const auto res5 = parser.parseArgs(v5.size(), &v5[0]);
|
||||
const auto& opts5 = res5.first;
|
||||
const auto& otherArgs5 = res5.second;
|
||||
|
||||
SG_VERIFY(opts5.empty());
|
||||
SG_CHECK_EQUAL(otherArgs5.size(), 4);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs5,
|
||||
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 6: use the '--' delimiter before what would otherwise be considered
|
||||
// an option
|
||||
const vector<const char*> v6({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "--", "-T",
|
||||
"non option 1", "non option 2", "--", "non option 3"});
|
||||
const auto res6 = parser.parseArgs(v6.size(), &v6[0]);
|
||||
const auto& opts6 = res6.first;
|
||||
const auto& otherArgs6 = res6.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts6.size(), 2);
|
||||
SG_CHECK_EQUAL(otherArgs6.size(), 5);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs6,
|
||||
vector<string>({"-T", "non option 1", "non option 2", "--",
|
||||
"non option 3"}));
|
||||
|
||||
// Test 7: use the '--' delimiter before an argument that doesn't look like
|
||||
// an option
|
||||
const vector<const char*> v7({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "--",
|
||||
"doesn't look like an option", "non option 1", "non option 2", "--",
|
||||
"non option 3"});
|
||||
const auto res7 = parser.parseArgs(v7.size(), &v7[0]);
|
||||
const auto& opts7 = res7.first;
|
||||
const auto& otherArgs7 = res7.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts7.size(), 2);
|
||||
SG_CHECK_EQUAL(otherArgs7.size(), 5);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs7,
|
||||
vector<string>({"doesn't look like an option", "non option 1",
|
||||
"non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 8: the argument marking the end of options is the empty string
|
||||
const vector<const char*> v8({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval",
|
||||
"", "non option 1", "non option 2", "-", "non option 3"});
|
||||
const auto res8 = parser.parseArgs(v8.size(), &v8[0]);
|
||||
const auto& opts8 = res8.first;
|
||||
const auto& otherArgs8 = res8.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts8.size(), 2);
|
||||
SG_CHECK_EQUAL(otherArgs8.size(), 5);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs8,
|
||||
vector<string>({"", "non option 1", "non option 2", "-", "non option 3"}));
|
||||
|
||||
// Test 9: the argument marking the end of options is a single hyphen
|
||||
const vector<const char*> v9({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval",
|
||||
"-", "non option 1", "non option 2", "-", "non option 3"});
|
||||
const auto res9 = parser.parseArgs(v9.size(), &v9[0]);
|
||||
const auto& opts9 = res9.first;
|
||||
const auto& otherArgs9 = res9.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts9.size(), 2);
|
||||
SG_CHECK_EQUAL(otherArgs9.size(), 5);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs9,
|
||||
vector<string>({"-", "non option 1", "non option 2", "-", "non option 3"}));
|
||||
|
||||
// Test 10: no other argument than the program name in argv
|
||||
const vector<const char*> v10({"FoobarProg"});
|
||||
const auto res10 = parser.parseArgs(v10.size(), &v10[0]);
|
||||
const auto& opts10 = res10.first;
|
||||
const auto& otherArgs10 = res10.second;
|
||||
|
||||
SG_VERIFY(opts10.empty());
|
||||
SG_VERIFY(otherArgs10.empty());
|
||||
}
|
||||
|
||||
void test_optionsWithMultipleAliases()
|
||||
{
|
||||
cout << "Testing options with multiple aliases" << endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT,
|
||||
vector<string>({"-o", "-O", "-0"}),
|
||||
vector<string>({"--o-alias-1", "--o-alias-2"}));
|
||||
parser.addOption("option -a", OptionArgType::MANDATORY_ARGUMENT,
|
||||
vector<string>({"-a", "-r"}),
|
||||
vector<string>({"--a-alias-1", "--a-alias-2",
|
||||
"--a-alias-3"}));
|
||||
parser.addOption("option -N", OptionArgType::NO_ARGUMENT,
|
||||
vector<string>({"-N", "-p"}),
|
||||
vector<string>({"--N-alias-1", "--N-alias-2"}));
|
||||
|
||||
const vector<const char*> v({
|
||||
"FoobarProg", "--o-alias-1", "-aarg for -a", "-pO", "arg for -O",
|
||||
"--a-alias-2=value 1", "--o-alias-2", "value 2", "-Novalue 3",
|
||||
"--N-alias-2", "--a-alias-3=value 4", "-0value 5", "--N-alias-1",
|
||||
"non option 1", "non option 2", "non option 3"});
|
||||
// v.size() corresponds to argc, &v[0] corresponds to argv.
|
||||
const auto res = parser.parseArgs(v.size(), &v[0]);
|
||||
const auto& opts = res.first;
|
||||
const auto& otherArgs = res.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts.size(), 12); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs.size(), 3); // number of non-option arguments
|
||||
|
||||
SG_CHECK_EQUAL(opts[0].passedAs(), "--o-alias-1");
|
||||
SG_CHECK_EQUAL(opts[0].value(), "");
|
||||
SG_CHECK_EQUAL(opts[0].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[0].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].passedAs(), "-a");
|
||||
SG_CHECK_EQUAL(opts[1].value(), "arg for -a");
|
||||
SG_CHECK_EQUAL(opts[1].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[1].id(), "option -a");
|
||||
|
||||
SG_CHECK_EQUAL(opts[2].passedAs(), "-p");
|
||||
SG_CHECK_EQUAL(opts[2].value(), "");
|
||||
SG_CHECK_EQUAL(opts[2].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[2].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL(opts[3].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[3].value(), "arg for -O");
|
||||
SG_CHECK_EQUAL(opts[3].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[3].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[4].passedAs(), "--a-alias-2");
|
||||
SG_CHECK_EQUAL(opts[4].value(), "value 1");
|
||||
SG_CHECK_EQUAL(opts[4].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[4].id(), "option -a");
|
||||
|
||||
SG_CHECK_EQUAL(opts[5].passedAs(), "--o-alias-2");
|
||||
SG_CHECK_EQUAL(opts[5].value(), "value 2");
|
||||
SG_CHECK_EQUAL(opts[5].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[5].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[6].passedAs(), "-N");
|
||||
SG_CHECK_EQUAL(opts[6].value(), "");
|
||||
SG_CHECK_EQUAL(opts[6].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[6].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL(opts[7].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[7].value(), "value 3");
|
||||
SG_CHECK_EQUAL(opts[7].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[7].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[8].passedAs(), "--N-alias-2");
|
||||
SG_CHECK_EQUAL(opts[8].value(), "");
|
||||
SG_CHECK_EQUAL(opts[8].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[8].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL(opts[9].passedAs(), "--a-alias-3");
|
||||
SG_CHECK_EQUAL(opts[9].value(), "value 4");
|
||||
SG_CHECK_EQUAL(opts[9].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[9].id(), "option -a");
|
||||
|
||||
SG_CHECK_EQUAL(opts[10].passedAs(), "-0");
|
||||
SG_CHECK_EQUAL(opts[10].value(), "value 5");
|
||||
SG_CHECK_EQUAL(opts[10].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[10].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[11].passedAs(), "--N-alias-1");
|
||||
SG_CHECK_EQUAL(opts[11].value(), "");
|
||||
SG_CHECK_EQUAL(opts[11].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[11].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs,
|
||||
vector<string>({"non option 1", "non option 2", "non option 3"}));
|
||||
}
|
||||
|
||||
// Auxiliary function used by test_invalidOptionOrArgumentMissing()
|
||||
void aux_invalidOptionOrMissingArgument_checkRaiseExcecption(
|
||||
const simgear::argparse::ArgumentParser& parser,
|
||||
const vector<const char*>& v)
|
||||
{
|
||||
bool gotException = false;
|
||||
|
||||
try {
|
||||
parser.parseArgs(v.size(), &v[0]);
|
||||
} catch (const simgear::argparse::Error&) {
|
||||
gotException = true;
|
||||
}
|
||||
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
void test_invalidOptionOrMissingArgument()
|
||||
{
|
||||
cout << "Testing passing invalid options and other syntax errors" << endl;
|
||||
|
||||
using simgear::argparse::OptionArgType;
|
||||
|
||||
simgear::argparse::ArgumentParser parser;
|
||||
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT, "-o");
|
||||
parser.addOption("option -m", OptionArgType::MANDATORY_ARGUMENT,
|
||||
"-m", "--mandatory-arg");
|
||||
parser.addOption("option -n", OptionArgType::NO_ARGUMENT, "-n", "--no-arg");
|
||||
|
||||
const vector<vector<const char*> > listOfArgvs({
|
||||
{"FoobarProg", "-ovalue", "-n", "-X",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-nXn",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--non-existent-option",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--non-existent-option=value",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "-m", "--",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "-X", "-m"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--mandatory-arg", "--",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--mandatory-arg"}
|
||||
});
|
||||
|
||||
for (const auto& argv: listOfArgvs) {
|
||||
aux_invalidOptionOrMissingArgument_checkRaiseExcecption(parser, argv);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char *const *argv)
|
||||
{
|
||||
test_mixOfShortAndLongOptions();
|
||||
test_whenOptionValueIsASingleHyphen();
|
||||
test_frontierBetweenOptionsAndNonOptions();
|
||||
test_optionsWithMultipleAliases();
|
||||
test_invalidOptionOrMissingArgument();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
@@ -53,9 +53,9 @@ static const char sgDirPathSep = '/';
|
||||
static const char sgDirPathSepBad = '\\';
|
||||
|
||||
#ifdef _WIN32
|
||||
const char SGPath::pathListSep = ';';
|
||||
const char SGPath::pathListSep[] = ";"; // this is null-terminated
|
||||
#else
|
||||
const char SGPath::pathListSep = ':';
|
||||
const char SGPath::pathListSep[] = ":"; // ditto
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -278,7 +278,6 @@ SGPath& SGPath::operator=(const SGPath& p)
|
||||
SGPath::~SGPath() {
|
||||
}
|
||||
|
||||
#if defined(ENABLE_OLD_PATH_API)
|
||||
// set path
|
||||
void SGPath::set( const string& p ) {
|
||||
path = p;
|
||||
@@ -286,7 +285,6 @@ void SGPath::set( const string& p ) {
|
||||
_cached = false;
|
||||
_rwCached = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void SGPath::setPermissionChecker(PermissionChecker validator)
|
||||
@@ -334,7 +332,7 @@ SGPath SGPath::operator/( const std::string& p ) const
|
||||
#if defined(ENABLE_OLD_PATH_API)
|
||||
//add a new path component to the existing path string
|
||||
void SGPath::add( const string& p ) {
|
||||
append( SGPath::pathListSep+p );
|
||||
append( SGPath::pathListSep[0] + p );
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -534,10 +532,11 @@ void SGPath::validate() const
|
||||
//------------------------------------------------------------------------------
|
||||
void SGPath::checkAccess() const
|
||||
{
|
||||
if( _rwCached && _cacheEnabled )
|
||||
if ( _rwCached && _cacheEnabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
validate();
|
||||
validate();
|
||||
_rwCached = true;
|
||||
}
|
||||
|
||||
@@ -666,7 +665,7 @@ string_list sgPathSplit( const string &search_path ) {
|
||||
bool done = false;
|
||||
|
||||
while ( !done ) {
|
||||
int index = tmp.find(SGPath::pathListSep);
|
||||
int index = tmp.find(SGPath::pathListSep[0]);
|
||||
if (index >= 0) {
|
||||
result.push_back( tmp.substr(0, index) );
|
||||
tmp = tmp.substr( index + 1 );
|
||||
@@ -1055,3 +1054,23 @@ bool SGPath::permissionsAllowsWrite() const
|
||||
return _permission_checker ? _permission_checker(*this).write : true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string SGPath::fileUrl() const
|
||||
{
|
||||
// we should really URL encode the names here?
|
||||
if (isAbsolute()) {
|
||||
// check for a windows drive letter
|
||||
#if defined(SG_WINDOWS)
|
||||
if (isalpha(path.front())) {
|
||||
// file URLs on Windows must look like file:///C:/Foo/Bar
|
||||
return "file:///" + utf8Str();
|
||||
}
|
||||
#endif
|
||||
// the leading directory seperator of the path becomes the required
|
||||
// third slash in this case.
|
||||
return "file://" + utf8Str();
|
||||
} else {
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Cannot convert relative path to a URL:" << path);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ class SGPath {
|
||||
|
||||
public:
|
||||
|
||||
// OS-dependent separator used in paths lists
|
||||
static const char pathListSep;
|
||||
// OS-dependent separator used in paths lists (C-style string of length 1)
|
||||
static const char pathListSep[2];
|
||||
|
||||
struct Permissions
|
||||
{
|
||||
@@ -281,6 +281,11 @@ public:
|
||||
*/
|
||||
SGPath dirPath() const;
|
||||
|
||||
/*
|
||||
* return path as a file:// URI
|
||||
*/
|
||||
std::string fileUrl() const;
|
||||
|
||||
enum StandardLocation
|
||||
{
|
||||
HOME,
|
||||
|
||||
@@ -20,12 +20,15 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <ctype.h>
|
||||
#include <cstring>
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <string.h> // strerror_r() and strerror_s()
|
||||
#include <errno.h>
|
||||
#include <type_traits>
|
||||
#include <cstring> // strerror_r() and strerror_s()
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
|
||||
#if defined(HAVE_CPP11_CODECVT)
|
||||
#include <codecvt> // new in C++11
|
||||
@@ -372,6 +375,170 @@ namespace simgear {
|
||||
return result;
|
||||
}
|
||||
|
||||
template<>
|
||||
int digitValue<10>(char c)
|
||||
{
|
||||
if ('0' <= c && c <= '9') {
|
||||
return static_cast<int>(c - '0');
|
||||
} else {
|
||||
throw sg_range_exception("invalid as a decimal digit: '" +
|
||||
std::string(1, c) + "'");
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
int digitValue<16>(char c)
|
||||
{
|
||||
if ('0' <= c && c <= '9') {
|
||||
return static_cast<int>(c - '0');
|
||||
} else if ('a' <= c && c <= 'f') {
|
||||
return 10 + static_cast<int>(c - 'a');
|
||||
} else if ('A' <= c && c <= 'F') {
|
||||
return 10 + static_cast<int>(c - 'A');
|
||||
} else {
|
||||
throw sg_range_exception("invalid as an hexadecimal digit: '" +
|
||||
std::string(1, c) + "'");
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string numerationBaseAdjective<10>()
|
||||
{ return std::string("decimal"); }
|
||||
|
||||
template<>
|
||||
std::string numerationBaseAdjective<16>()
|
||||
{ return std::string("hexadecimal"); }
|
||||
|
||||
template<class T, int BASE, typename>
|
||||
T readNonNegativeInt(const std::string& s)
|
||||
{
|
||||
static_assert(0 < BASE,
|
||||
"template value BASE must be a positive integer");
|
||||
static_assert(BASE <= std::numeric_limits<T>::max(),
|
||||
"template type T too small: it cannot represent BASE");
|
||||
T res(0);
|
||||
T multiplier(1);
|
||||
T increment;
|
||||
int digit;
|
||||
|
||||
if (s.empty()) {
|
||||
throw sg_format_exception("expected a non-empty string", s);
|
||||
}
|
||||
|
||||
for (auto it = s.crbegin(); it != s.crend(); it++) {
|
||||
if (it != s.crbegin()) {
|
||||
// Check if 'multiplier *= BASE' is going to overflow. This is
|
||||
// reliable because 'multiplier' and 'BASE' are positive.
|
||||
if (multiplier > std::numeric_limits<T>::max() / BASE) {
|
||||
// If all remaining digits are '0', it doesn't matter that
|
||||
// the multiplier overflows.
|
||||
if (std::all_of(it, s.crend(),
|
||||
[](char c){ return (c == '0'); })) {
|
||||
return res;
|
||||
} else {
|
||||
throw sg_range_exception(
|
||||
"doesn't fit in the specified type: '" + s + "'");
|
||||
}
|
||||
}
|
||||
multiplier *= BASE;
|
||||
}
|
||||
|
||||
try {
|
||||
digit = digitValue<BASE>(*it);
|
||||
} catch (const sg_range_exception&) {
|
||||
throw sg_format_exception(
|
||||
"expected a string containing " +
|
||||
numerationBaseAdjective<BASE>() +
|
||||
" digits only, but got '" + s + "'", s);
|
||||
}
|
||||
|
||||
// Reliable because 'multiplier' is positive
|
||||
if (digit > 0 &&
|
||||
multiplier > std::numeric_limits<T>::max() / digit) {
|
||||
throw sg_range_exception(
|
||||
"doesn't fit in the specified type: '" + s + "'");
|
||||
}
|
||||
increment = multiplier*digit;
|
||||
|
||||
if (res > std::numeric_limits<T>::max() - increment) {
|
||||
throw sg_range_exception(
|
||||
"doesn't fit in the specified type: '" + s + "'");
|
||||
}
|
||||
res += increment;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Explicit template instantiations.
|
||||
//
|
||||
// In order to save some bytes for the SimGearCore library[*], we only
|
||||
// instantiate a small number of variants of readNonNegativeInt() below.
|
||||
// Just enable the ones you need if they are disabled.
|
||||
//
|
||||
// [*] The exact amount depends a lot on what you measure and in which
|
||||
// circumstances. On Linux amd64 with g++, I measured a cost ranging
|
||||
// from 2 KB per template in a Release build to 19 KB per template in
|
||||
// a RelWithDebInfo build for the in-memory code size of the resulting
|
||||
// fgfs binary (CODE column in 'top', after selecting a suitable
|
||||
// unit). If I look at the fgfs binary size (statically-linked with
|
||||
// SimGear), I measure from 2 KB per template (Release) to 30 KB per
|
||||
// template (RelWithDebInfo). Finally, a Debug build compiled with
|
||||
// '-fno-omit-frame-pointer -O0 -fno-inline' lies between the Release
|
||||
// and the RelWithDebInfo builds.
|
||||
|
||||
#if 0
|
||||
template
|
||||
signed char readNonNegativeInt<signed char, 10>(const std::string& s);
|
||||
template
|
||||
signed char readNonNegativeInt<signed char, 16>(const std::string& s);
|
||||
template
|
||||
unsigned char readNonNegativeInt<unsigned char, 10>(const std::string& s);
|
||||
template
|
||||
unsigned char readNonNegativeInt<unsigned char, 16>(const std::string& s);
|
||||
|
||||
template
|
||||
short readNonNegativeInt<short, 10>(const std::string& s);
|
||||
template
|
||||
short readNonNegativeInt<short, 16>(const std::string& s);
|
||||
template
|
||||
unsigned short readNonNegativeInt<unsigned short, 10>(const std::string& s);
|
||||
template
|
||||
unsigned short readNonNegativeInt<unsigned short, 16>(const std::string& s);
|
||||
#endif
|
||||
|
||||
template
|
||||
int readNonNegativeInt<int, 10>(const std::string& s);
|
||||
template
|
||||
unsigned int readNonNegativeInt<unsigned int, 10>(const std::string& s);
|
||||
|
||||
#if 0
|
||||
template
|
||||
int readNonNegativeInt<int, 16>(const std::string& s);
|
||||
template
|
||||
unsigned int readNonNegativeInt<unsigned int, 16>(const std::string& s);
|
||||
|
||||
template
|
||||
long readNonNegativeInt<long, 10>(const std::string& s);
|
||||
template
|
||||
long readNonNegativeInt<long, 16>(const std::string& s);
|
||||
template
|
||||
unsigned long readNonNegativeInt<unsigned long, 10>(const std::string& s);
|
||||
template
|
||||
unsigned long readNonNegativeInt<unsigned long, 16>(const std::string& s);
|
||||
|
||||
template
|
||||
long long readNonNegativeInt<long long, 10>(const std::string& s);
|
||||
template
|
||||
long long readNonNegativeInt<long long, 16>(const std::string& s);
|
||||
template
|
||||
unsigned long long readNonNegativeInt<unsigned long long, 10>(
|
||||
const std::string& s);
|
||||
template
|
||||
unsigned long long readNonNegativeInt<unsigned long long, 16>(
|
||||
const std::string& s);
|
||||
#endif
|
||||
|
||||
int compare_versions(const string& v1, const string& v2, int maxComponents)
|
||||
{
|
||||
vector<string> v1parts(split(v1, "."));
|
||||
@@ -630,6 +797,59 @@ std::string encodeHex(const unsigned char* rawBytes, unsigned int length)
|
||||
return hex;
|
||||
}
|
||||
|
||||
// Write an octal backslash-escaped respresentation of 'val' to 'buf'.
|
||||
//
|
||||
// At least 4 write positions must be available at 'buf'. The result is *not*
|
||||
// null-terminated. Only the 8 least significant bits of 'val' are used;
|
||||
// higher-order bits have no influence on the chars written to 'buf'.
|
||||
static void writeOctalBackslashEscapedRepr(char *buf, unsigned char val)
|
||||
{
|
||||
buf[0] = '\\';
|
||||
buf[1] = '0' + ((val >> 6) & 3); // 2 bits
|
||||
buf[2] = '0' + ((val >> 3) & 7); // 3 bits
|
||||
buf[3] = '0' + (val & 7); // 3 bits
|
||||
}
|
||||
|
||||
// Backslash-escape a string for C/C++ string literal syntax.
|
||||
std::string escape(const std::string& s) {
|
||||
string res;
|
||||
char buf[4];
|
||||
|
||||
for (const char c: s) {
|
||||
// We don't really *need* to special-case \a, \b, \f, \n, \r, \t and \v,
|
||||
// because they could be handled like the other non-ASCII or non-printable
|
||||
// characters. However, doing so will make the output string both shorter
|
||||
// and more readable.
|
||||
if (c == '\a') {
|
||||
res += "\\a";
|
||||
} else if (c == '\b') {
|
||||
res += "\\b";
|
||||
} else if (c == '\f') {
|
||||
res += "\\f";
|
||||
} else if (c == '\n') {
|
||||
res += "\\n";
|
||||
} else if (c == '\r') {
|
||||
res += "\\r";
|
||||
} else if (c == '\t') {
|
||||
res += "\\t";
|
||||
} else if (c == '\v') {
|
||||
res += "\\v";
|
||||
} else if (c < 0x20 || c > 0x7e) { // non-ASCII or non-printable character
|
||||
// This is fast (no memory allocation nor IOStreams needed)
|
||||
writeOctalBackslashEscapedRepr(buf, static_cast<unsigned char>(c));
|
||||
res.append(buf, 4);
|
||||
} else if (c == '\\') {
|
||||
res += "\\\\";
|
||||
} else if (c == '"') {
|
||||
res += "\\\"";
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string unescape(const char* s)
|
||||
{
|
||||
@@ -665,16 +885,17 @@ std::string unescape(const char* s)
|
||||
if (!*++s)
|
||||
break;
|
||||
int v = 0;
|
||||
for (int i = 0; i < 2 && isxdigit(*s); i++, s++)
|
||||
for (/* empty */; isxdigit(*s); s++) {
|
||||
v = v * 16 + (isdigit(*s) ? *s - '0' : 10 + tolower(*s) - 'a');
|
||||
r += v;
|
||||
}
|
||||
r += static_cast<char>(v);
|
||||
continue;
|
||||
|
||||
} else if (*s >= '0' && *s <= '7') {
|
||||
int v = *s++ - '0';
|
||||
for (int i = 0; i < 3 && *s >= '0' && *s <= '7'; i++, s++)
|
||||
for (int i = 0; i < 2 && *s >= '0' && *s <= '7'; i++, s++)
|
||||
v = v * 8 + *s - '0';
|
||||
r += v;
|
||||
r += static_cast<char>(v);
|
||||
continue;
|
||||
|
||||
} else {
|
||||
@@ -739,6 +960,94 @@ std::string error_string(int errnum)
|
||||
#endif // !defined(_GNU_SOURCE)
|
||||
}
|
||||
|
||||
bool to_bool(const std::string& s)
|
||||
{
|
||||
if (!strcasecmp(s.c_str(), "yes")) return true;
|
||||
if (!strcasecmp(s.c_str(), "no")) return false;
|
||||
if (!strcasecmp(s.c_str(), "true")) return true;
|
||||
if (!strcasecmp(s.c_str(), "false")) return false;
|
||||
if (s == "1") return true;
|
||||
if (s == "0") return false;
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Unable to parse string as boolean:" << s);
|
||||
return false;
|
||||
}
|
||||
|
||||
enum PropMatchState
|
||||
{
|
||||
MATCH_LITERAL = 0,
|
||||
MATCH_WILD_INDEX,
|
||||
MATCH_WILD_NAME
|
||||
};
|
||||
|
||||
bool matchPropPathToTemplate(const std::string& path, const std::string& templatePath)
|
||||
{
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* pathPtr = path.c_str();
|
||||
const char* tPtr = templatePath.c_str();
|
||||
PropMatchState state = MATCH_LITERAL;
|
||||
|
||||
while (true) {
|
||||
bool advanceInTemplate = true;
|
||||
const char p = *pathPtr;
|
||||
if (p == 0) {
|
||||
// ran out of chars in the path. If we are matching a trailing
|
||||
// wildcard, this is a match, otherwise it's a fail
|
||||
if (state == MATCH_WILD_NAME) {
|
||||
// check this is the last * in the template string
|
||||
if (*(tPtr + 1) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case MATCH_LITERAL:
|
||||
if (*tPtr != p) {
|
||||
// literal mismatch
|
||||
return false;
|
||||
}
|
||||
++pathPtr;
|
||||
break;
|
||||
case MATCH_WILD_NAME:
|
||||
if ((p == '-') || isalpha(p)) {
|
||||
advanceInTemplate = false;
|
||||
++pathPtr;
|
||||
} else {
|
||||
// something else, we will advance in the template
|
||||
}
|
||||
break;
|
||||
case MATCH_WILD_INDEX:
|
||||
if (isdigit(p)) {
|
||||
advanceInTemplate = false;
|
||||
++pathPtr;
|
||||
} else {
|
||||
// something else, we will advance in the template
|
||||
}
|
||||
break;
|
||||
} // of state switch
|
||||
|
||||
if (advanceInTemplate) {
|
||||
const char nextTemplate = *(++tPtr);
|
||||
if (nextTemplate == 0) {
|
||||
// end of template, successful match
|
||||
return true;
|
||||
} else if (nextTemplate == '*') {
|
||||
state = (*(tPtr - 1) == '[') ? MATCH_WILD_INDEX : MATCH_WILD_NAME;
|
||||
} else {
|
||||
state = MATCH_LITERAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unreachable
|
||||
}
|
||||
|
||||
} // end namespace strutils
|
||||
|
||||
} // end namespace simgear
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <cstdlib>
|
||||
|
||||
typedef std::vector < std::string > string_list;
|
||||
@@ -168,7 +169,52 @@ namespace simgear {
|
||||
* convert a string representing a decimal number, to an int
|
||||
*/
|
||||
int to_int(const std::string& s, int base = 10);
|
||||
|
||||
|
||||
/** Convert a char to the integer it represents in the specified BASE.
|
||||
*
|
||||
* Contrary to std::isdigit() and std::isxdigit(), only the standard ASCII
|
||||
* digits for BASE are accepted (with both uppercase and lowercase 'a'-'f'
|
||||
* letters for base 16). Throw sg_range_exception if the char is not a
|
||||
* valid digit for this base.
|
||||
*
|
||||
* See template specializations in strutils.cxx.
|
||||
*/
|
||||
template<int BASE>
|
||||
int digitValue(char c);
|
||||
|
||||
/** Return:
|
||||
* - std::string("decimal") if BASE is 10;
|
||||
* - std::string("hexadecimal") if BASE is 16.
|
||||
*
|
||||
* Template specializations in strutils.cxx.
|
||||
*/
|
||||
template<int BASE>
|
||||
std::string numerationBaseAdjective();
|
||||
|
||||
/** Convert a string representing an integer to an integral type.
|
||||
*
|
||||
* The input string must be non-empty and contain only digits of the
|
||||
* specified BASE (template parameter). Throw:
|
||||
* - sg_format_exception if the input string doesn't respect these
|
||||
* constraints;
|
||||
* - sg_range_exception if the value can't be represented by type T
|
||||
* (i.e., if it is too large).
|
||||
*
|
||||
* Explicit template instantiations are added as needed in strutils.cxx.
|
||||
* Have a look there and enable the ones you need!
|
||||
*/
|
||||
template<
|
||||
class T,
|
||||
int BASE = 10,
|
||||
typename = typename std::enable_if<std::is_integral<T>::value, T>::type >
|
||||
T readNonNegativeInt(const std::string& s);
|
||||
|
||||
/**
|
||||
* Convert a string representing a boolean, to a bool.
|
||||
* Accepted values include YES, true, 0, 1, false, no, True,
|
||||
*/
|
||||
bool to_bool(const std::string& s);
|
||||
|
||||
/**
|
||||
* Like strcmp(), but for dotted versions strings NN.NN.NN
|
||||
* any number of terms are supported.
|
||||
@@ -223,26 +269,43 @@ namespace simgear {
|
||||
* malformed
|
||||
*/
|
||||
void decodeBase64(const std::string& a, std::vector<unsigned char>& output);
|
||||
|
||||
|
||||
/**
|
||||
* convert bytes to hexadecimal equivalent
|
||||
*/
|
||||
std::string encodeHex(const std::string& bytes);
|
||||
|
||||
|
||||
std::string encodeHex(const unsigned char* rawBytes, unsigned int length);
|
||||
|
||||
/**
|
||||
* Backslash-escape a string for C/C++ string literal syntax.
|
||||
*
|
||||
* @param s Input string.
|
||||
* @return a copy of the input string with proper escaping, so that if the
|
||||
* result is part of a C or C++ file and enclosed in double
|
||||
* quotes, it can be used to represent a string literal that is
|
||||
* equal to the input string.
|
||||
*
|
||||
* @note For every std::string s: unescape(escape(s)) == s
|
||||
* @see unescape()
|
||||
*/
|
||||
std::string escape(const std::string& s);
|
||||
|
||||
/**
|
||||
* Unescape string.
|
||||
*
|
||||
* @param str String possibly containing escaped characters.
|
||||
* @return string with escaped characters replaced by single character
|
||||
* values.
|
||||
*
|
||||
* @note For every std::string s: unescape(escape(s)) == s
|
||||
* @see escape()
|
||||
*/
|
||||
std::string unescape(const char* str);
|
||||
|
||||
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
|
||||
@@ -258,6 +321,20 @@ namespace simgear {
|
||||
*/
|
||||
std::string error_string(int errnum);
|
||||
|
||||
|
||||
/**
|
||||
* Match a property path, obtained from prop->getPath(), against a
|
||||
* template string. Templates are allowed to contain widlcards denoted by
|
||||
* an asterix in certain places - at the end of names, or inside indices.
|
||||
* Note that paths returned by getPath() always include an index on every
|
||||
* path component, so template strings should be structured accordingly.
|
||||
*
|
||||
* Examples:
|
||||
* /foo[*]/bar* will match /foo/barber, /foo[2]/bargain
|
||||
* /views[0]/view[*]/f* will match /views[0]/view[99]/foo,
|
||||
* /views[0]/view[4]/fig, /views[0]/view[1000]/flight
|
||||
*/
|
||||
bool matchPropPathToTemplate(const std::string& path, const std::string& templatePath);
|
||||
} // end namespace strutils
|
||||
} // end namespace simgear
|
||||
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// Unit tests for functions inside the strutils package
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h> // _set_errno() on Windows
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility> // std::move()
|
||||
#include <fstream> // std::ifstream
|
||||
#include <sstream> // std::ostringstream
|
||||
#include <ios> // std::dec, std::hex
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <typeinfo> // typeid()
|
||||
#include <cstdint> // uint16_t, uintmax_t, etc.
|
||||
#include <cstdlib> // _set_errno() on Windows
|
||||
#include <cerrno>
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
@@ -85,6 +97,290 @@ void test_to_int()
|
||||
SG_CHECK_EQUAL(strutils::to_int("-10000"), -10000);
|
||||
}
|
||||
|
||||
// Auxiliary function for test_readNonNegativeInt()
|
||||
void aux_readNonNegativeInt_setUpOStringStream(std::ostringstream& oss, int base)
|
||||
{
|
||||
switch (base) {
|
||||
case 10:
|
||||
oss << std::dec;
|
||||
break;
|
||||
case 16:
|
||||
oss << std::hex;
|
||||
break;
|
||||
default:
|
||||
SG_TEST_FAIL("unsupported value for 'base': " + std::to_string(base));
|
||||
}
|
||||
}
|
||||
|
||||
// Auxiliary function for test_readNonNegativeInt(): round-trip conversion for
|
||||
// the given number of values below and up to std::numeric_limits<T>::max().
|
||||
template<typename T, int BASE>
|
||||
void aux_readNonNegativeInt_testValuesCloseToMax(T nbValues)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
assert(0 <= nbValues && nbValues <= std::numeric_limits<T>::max());
|
||||
aux_readNonNegativeInt_setUpOStringStream(oss, BASE);
|
||||
|
||||
for (T i = std::numeric_limits<T>::max() - nbValues;
|
||||
i < std::numeric_limits<T>::max(); i++) {
|
||||
T valueToTest = i + 1;
|
||||
T roundTripResult;
|
||||
bool gotException = false;
|
||||
|
||||
oss.str("");
|
||||
// The cast is only useful when T is a char type
|
||||
oss << static_cast<uintmax_t>(valueToTest);
|
||||
|
||||
try {
|
||||
roundTripResult = strutils::readNonNegativeInt<T, BASE>(oss.str());
|
||||
} catch (const sg_range_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
|
||||
SG_VERIFY(!gotException);
|
||||
SG_CHECK_EQUAL(roundTripResult, valueToTest);
|
||||
}
|
||||
}
|
||||
|
||||
// Auxiliary class for test_readNonNegativeInt(): test that we do get an
|
||||
// exception when trying to convert the smallest, positive out-of-range value
|
||||
// for type T.
|
||||
template<typename T, int BASE>
|
||||
class ReadNonNegativeInt_JustOutOfRangeTester {
|
||||
public:
|
||||
ReadNonNegativeInt_JustOutOfRangeTester()
|
||||
{ }
|
||||
|
||||
// Run the test
|
||||
void run()
|
||||
{
|
||||
std::ostringstream oss;
|
||||
aux_readNonNegativeInt_setUpOStringStream(oss, BASE);
|
||||
oss << 1 + static_cast<uintmax_t>(std::numeric_limits<T>::max());
|
||||
bool gotException = false;
|
||||
|
||||
try {
|
||||
strutils::readNonNegativeInt<T, BASE>(oss.str());
|
||||
} catch (const sg_range_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
};
|
||||
|
||||
class ReadNonNegativeInt_DummyTester {
|
||||
public:
|
||||
ReadNonNegativeInt_DummyTester()
|
||||
{ }
|
||||
|
||||
void run()
|
||||
{ }
|
||||
};
|
||||
|
||||
// We use this helper class to automatically determine for which types
|
||||
// ReadNonNegativeInt_JustOutOfRangeTester::run() can be run.
|
||||
template<typename T, int BASE>
|
||||
class AuxReadNonNegativeInt_JustOutOfRange_Helper
|
||||
{
|
||||
typedef typename std::make_unsigned<T>::type uT;
|
||||
|
||||
// Define TestRunner to be either
|
||||
//
|
||||
// ReadNonNegativeInt_JustOutOfRangeTester<T, BASE>
|
||||
//
|
||||
// or
|
||||
//
|
||||
// ReadNonNegativeInt_DummyTester
|
||||
//
|
||||
// depending on whether 1 + std::numeric_limits<T>::max() can be
|
||||
// represented by uintmax_t.
|
||||
typedef typename std::conditional<
|
||||
static_cast<uT>(std::numeric_limits<T>::max()) <
|
||||
std::numeric_limits<uintmax_t>::max(),
|
||||
ReadNonNegativeInt_JustOutOfRangeTester<T, BASE>,
|
||||
ReadNonNegativeInt_DummyTester >::type TestRunner;
|
||||
|
||||
public:
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper()
|
||||
{ };
|
||||
|
||||
void test()
|
||||
{
|
||||
TestRunner().run();
|
||||
}
|
||||
};
|
||||
|
||||
void test_readNonNegativeInt()
|
||||
{
|
||||
// In order to save some bytes for the SimGearCore library[*], we only
|
||||
// instantiated a small number of variants of readNonNegativeInt() in
|
||||
// strutils.cxx. This is why many tests are disabled with '#if 0' below. Of
|
||||
// course, more variants can be enabled when they are needed.
|
||||
//
|
||||
// [*] See measures in strutils.cxx before the template instantiations.
|
||||
|
||||
#if 0
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<short>("0")), 0);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<short>("23")), 23);
|
||||
#endif
|
||||
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int>("0")), 0);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int>("00000000")), 0);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int>("12345")), 12345);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int, 10>("12345")), 12345);
|
||||
|
||||
#if 0
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int, 16>("ff")), 0xff);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int, 16>("a5E9")), 0xa5e9);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<unsigned long, 16>("0cda")),
|
||||
0x0cda);
|
||||
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<uint16_t, 10>("65535")), 0xffff);
|
||||
SG_CHECK_EQUAL(
|
||||
(strutils::readNonNegativeInt<uint16_t, 10>("00000000000000000000065535")),
|
||||
0xffff);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<uint16_t, 16>("ffff")), 0xffff);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int16_t, 10>("32767")), 0x7fff);
|
||||
SG_CHECK_EQUAL((strutils::readNonNegativeInt<int16_t, 16>("7fff")), 0x7fff);
|
||||
#endif
|
||||
|
||||
// Nothing special about the values :)
|
||||
SG_CHECK_EQUAL_NOSTREAM((typeid(strutils::readNonNegativeInt<int, 10>("72"))),
|
||||
typeid(int(12)));
|
||||
#if 0
|
||||
SG_CHECK_EQUAL_NOSTREAM((typeid(strutils::readNonNegativeInt<long, 10>("72"))),
|
||||
typeid(12L));
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
(typeid(strutils::readNonNegativeInt<long long, 10>("72"))),
|
||||
typeid(12LL));
|
||||
#endif
|
||||
|
||||
{
|
||||
bool gotException = false;
|
||||
try {
|
||||
strutils::readNonNegativeInt<int>(""); // empty string: illegal
|
||||
} catch (const sg_format_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
{
|
||||
bool gotException = false;
|
||||
try {
|
||||
strutils::readNonNegativeInt<int>("-1"); // non-digit character: illegal
|
||||
} catch (const sg_format_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
{
|
||||
bool gotException = false;
|
||||
try {
|
||||
strutils::readNonNegativeInt<int>("+1"); // non-digit character: illegal
|
||||
} catch (const sg_format_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
{
|
||||
bool gotException = false;
|
||||
try {
|
||||
strutils::readNonNegativeInt<int>("858efe"); // trailing garbage: illegal
|
||||
} catch (const sg_format_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
#if 0
|
||||
{
|
||||
bool gotException = false;
|
||||
try {
|
||||
strutils::readNonNegativeInt<int, 16>("858g5k"); // ditto for base 16
|
||||
} catch (const sg_format_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
bool gotException = false;
|
||||
try {
|
||||
strutils::readNonNegativeInt<int>(" 858"); // leading whitespace/garbage:
|
||||
} catch (const sg_format_exception&) { // illegal too
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
// Try to read a value that is 1 unit too large for the type. Check that it
|
||||
// raises an sg_range_exception in each case.
|
||||
#if 0
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<signed char, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<signed char, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned char, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned char, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<short, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<short, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned short, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned short, 16>().test();
|
||||
#endif
|
||||
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<int, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned int, 10>().test();
|
||||
|
||||
#if 0
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<int, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned int, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<long, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<long, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<long long, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<long long, 16>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long long, 10>().test();
|
||||
AuxReadNonNegativeInt_JustOutOfRange_Helper<unsigned long long, 16>().test();
|
||||
#endif
|
||||
|
||||
// Round trip tests with large values, including the largest value that can
|
||||
// be represented by the type, in each case.
|
||||
//
|
||||
// Can be casted as any of the following types
|
||||
constexpr int nbValues = 5000;
|
||||
#if 0
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<signed char, 10>(127);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<signed char, 16>(127);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned char, 10>(127);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned char, 16>(127);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<short, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<short, 16>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned short, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned short, 16>(nbValues);
|
||||
#endif
|
||||
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<int, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned int, 10>(nbValues);
|
||||
|
||||
#if 0
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<int, 16>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned int, 16>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<long, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<long, 16>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long, 16>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<long long, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<long long, 16>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long long, 10>(nbValues);
|
||||
aux_readNonNegativeInt_testValuesCloseToMax<unsigned long long, 16>(nbValues);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_split()
|
||||
{
|
||||
string_list l = strutils::split("zero one two three four five");
|
||||
@@ -139,9 +435,72 @@ void test_split()
|
||||
}
|
||||
}
|
||||
|
||||
void test_escape()
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::escape(""), "");
|
||||
SG_CHECK_EQUAL(strutils::escape("\\"), "\\\\");
|
||||
SG_CHECK_EQUAL(strutils::escape("\""), "\\\"");
|
||||
SG_CHECK_EQUAL(strutils::escape("\\n"), "\\\\n");
|
||||
SG_CHECK_EQUAL(strutils::escape("n\\"), "n\\\\");
|
||||
SG_CHECK_EQUAL(strutils::escape(" ab\nc \\def\t\r \\ ghi\\"),
|
||||
" ab\\nc \\\\def\\t\\r \\\\ ghi\\\\");
|
||||
// U+0152 is LATIN CAPITAL LIGATURE OE. The last word is Egg translated in
|
||||
// French and encoded in UTF-8 ('Œuf' if you can read UTF-8).
|
||||
SG_CHECK_EQUAL(strutils::escape("Un \"Bel\" '\u0152uf'"),
|
||||
"Un \\\"Bel\\\" '\\305\\222uf'");
|
||||
SG_CHECK_EQUAL(strutils::escape("\a\b\f\n\r\t\v"),
|
||||
"\\a\\b\\f\\n\\r\\t\\v");
|
||||
|
||||
// Test with non-printable characters
|
||||
//
|
||||
// - 'prefix' is an std::string that *contains* a NUL character.
|
||||
// - \012 is \n (LINE FEED).
|
||||
// - \037 (\x1F) is the last non-printable ASCII character before \040 (\x20),
|
||||
// which is the space.
|
||||
// - \176 (\x7E) is '~', the last printable ASCII character.
|
||||
// - \377 is \xFF. Higher char values (> 255) are not faithfully encoded by
|
||||
// strutils::escape(): only the lowest 8 bits are used; higher-order bits
|
||||
// are ignored (for people who use chars with more than 8 bits...).
|
||||
const string prefix = string("abc") + '\000';
|
||||
SG_CHECK_EQUAL(strutils::escape(prefix +
|
||||
"\003def\012\037\040\176\177\376\377"),
|
||||
"abc\\000\\003def\\n\\037 ~\\177\\376\\377");
|
||||
|
||||
SG_CHECK_EQUAL(strutils::escape(" \n\tAOa"), " \\n\\tAOa");
|
||||
}
|
||||
|
||||
void test_unescape()
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\ \\n\\t\\x41\\117a"), " \n\tAOa");
|
||||
// Two chars: '\033' (ESC) followed by '2'
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\0332"), "\0332");
|
||||
// Hex escapes have no length limit and terminate at the first character
|
||||
// that is not a valid hexadecimal digit.
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\x00020|"), " |");
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\xA"), "\n");
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\xA-"), "\n-");
|
||||
}
|
||||
|
||||
void aux_escapeAndUnescapeRoundTripTest(const string& testString)
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::unescape(strutils::escape(testString)), testString);
|
||||
}
|
||||
|
||||
void test_escapeAndUnescapeRoundTrips()
|
||||
{
|
||||
// "\0332" contains two chars: '\033' (ESC) followed by '2'.
|
||||
// Ditto for "\0402": it's a space ('\040') followed by a '2'.
|
||||
vector<string> stringsToTest(
|
||||
{"", "\\", "\n", "\\\\", "\"\'\?\t\rAG\v\a \b\f\\", "\x23\xf8",
|
||||
"\0332", "\0402", "\u00e0", "\U000000E9"});
|
||||
|
||||
const string withBinary = (string("abc") + '\000' +
|
||||
"\003def\012\037\040\176\177\376\377");
|
||||
stringsToTest.push_back(std::move(withBinary));
|
||||
|
||||
for (const string& s: stringsToTest) {
|
||||
aux_escapeAndUnescapeRoundTripTest(s);
|
||||
}
|
||||
}
|
||||
|
||||
void test_compare_versions()
|
||||
@@ -175,6 +534,32 @@ void test_md5_hex()
|
||||
SG_CHECK_EQUAL(strutils::md5("test"), "098f6bcd4621d373cade4e832627b4f6");
|
||||
}
|
||||
|
||||
void test_propPathMatch()
|
||||
{
|
||||
const char* testTemplate1 = "/sim[*]/views[*]/render";
|
||||
SG_VERIFY(strutils::matchPropPathToTemplate("/sim[0]/views[50]/render-buildings[0]", testTemplate1));
|
||||
SG_VERIFY(strutils::matchPropPathToTemplate("/sim[1]/views[0]/rendering-enabled", testTemplate1));
|
||||
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/sim[0]/views[50]/something-else", testTemplate1));
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/sim[0]/gui[0]/wibble", testTemplate1));
|
||||
|
||||
// test explicit index matching
|
||||
const char* testTemplate2 = "/view[5]/*";
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/view[2]/render-buildings[0]", testTemplate2));
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/sim[1]/foo", testTemplate2));
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/view[50]/foo", testTemplate2));
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/view[55]/foo", testTemplate2));
|
||||
|
||||
SG_VERIFY(strutils::matchPropPathToTemplate("/view[5]/foo", testTemplate2));
|
||||
SG_VERIFY(strutils::matchPropPathToTemplate("/view[5]/child[3]/bar", testTemplate2));
|
||||
|
||||
|
||||
const char* testTemplate3 = "/*[*]/fdm*[*]/aero*";
|
||||
|
||||
SG_VERIFY(strutils::matchPropPathToTemplate("/position[2]/fdm-jsb[0]/aerodynamic", testTemplate3));
|
||||
SG_VERIFY(!strutils::matchPropPathToTemplate("/position[2]/foo[0]/aerodynamic", testTemplate3));
|
||||
}
|
||||
|
||||
void test_error_string()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
@@ -205,11 +590,15 @@ int main(int argc, char* argv[])
|
||||
test_ends_with();
|
||||
test_simplify();
|
||||
test_to_int();
|
||||
test_readNonNegativeInt();
|
||||
test_split();
|
||||
test_escape();
|
||||
test_unescape();
|
||||
test_escapeAndUnescapeRoundTrips();
|
||||
test_compare_versions();
|
||||
test_md5_hex();
|
||||
test_error_string();
|
||||
test_propPathMatch();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ set(SOURCES
|
||||
Package.cxx
|
||||
Install.cxx
|
||||
Root.cxx
|
||||
Delegate.cxx
|
||||
# internal helpers
|
||||
md5.h md5.c
|
||||
ioapi.c ioapi_mem.c ioapi.h
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
@@ -167,16 +167,14 @@ int parseTest()
|
||||
SG_CHECK_EQUAL(index->url, "http://foo.bar.com/thumb-panel.png");
|
||||
SG_VERIFY(index->type == pkg::Package::Preview::Type::PANEL);
|
||||
|
||||
// old-style thumbnails
|
||||
string_list oldThumbUrls = p2->thumbnailUrls();
|
||||
SG_CHECK_EQUAL(oldThumbUrls.size(), 1);
|
||||
SG_CHECK_EQUAL(oldThumbUrls.at(0), "http://foo.bar.com/thumb-exterior.png");
|
||||
|
||||
string_list oldThumbPaths = p2->thumbnails();
|
||||
SG_CHECK_EQUAL(oldThumbPaths.size(), 1);
|
||||
SG_CHECK_EQUAL(oldThumbPaths.at(0), "exterior.png");
|
||||
// thumbnails
|
||||
const pkg::Package::Thumbnail& thumb = p2->thumbnailForVariant(0);
|
||||
SG_CHECK_EQUAL(thumb.url, "http://foo.bar.com/thumb-exterior.png");
|
||||
SG_CHECK_EQUAL(thumb.path, "exterior.png");
|
||||
|
||||
// test variants
|
||||
SG_CHECK_EQUAL(p2->parentIdForVariant(0), std::string());
|
||||
|
||||
try {
|
||||
p2->indexOfVariant("fofofo");
|
||||
SG_TEST_FAIL("lookup of non-existant variant did not throw");
|
||||
@@ -190,6 +188,8 @@ int parseTest()
|
||||
unsigned int skisVariant = p2->indexOfVariant("c172p-skis");
|
||||
SG_VERIFY(skisVariant > 0);
|
||||
|
||||
SG_CHECK_EQUAL(p2->parentIdForVariant(skisVariantFull), "c172p");
|
||||
|
||||
SG_CHECK_EQUAL(skisVariant, skisVariantFull);
|
||||
|
||||
SG_CHECK_EQUAL(p2->getLocalisedProp("description", skisVariant),
|
||||
@@ -199,6 +199,7 @@ int parseTest()
|
||||
|
||||
unsigned int floatsVariant = p2->indexOfVariant("c172p-floats");
|
||||
SG_VERIFY(floatsVariant > 0);
|
||||
SG_CHECK_EQUAL(p2->parentIdForVariant(floatsVariant), "c172p");
|
||||
|
||||
SG_CHECK_EQUAL(p2->getLocalisedProp("description", floatsVariant),
|
||||
"A plane with floats");
|
||||
@@ -215,6 +216,22 @@ int parseTest()
|
||||
SG_CHECK_EQUAL(index->url, "http://foo.bar.com/thumb-exterior-skis.png");
|
||||
SG_VERIFY(index->type == pkg::Package::Preview::Type::EXTERIOR);
|
||||
|
||||
const pkg::Package::Thumbnail& thumb2 = p2->thumbnailForVariant(floatsVariant);
|
||||
SG_CHECK_EQUAL(thumb2.url, "http://foo.bar.com/thumb-floats.png");
|
||||
SG_CHECK_EQUAL(thumb2.path, "thumb-floats.png");
|
||||
|
||||
// test multiple primary
|
||||
unsigned int rVariant = p2->indexOfVariant("c172r");
|
||||
SG_VERIFY(rVariant > 0);
|
||||
|
||||
SG_CHECK_EQUAL(p2->parentIdForVariant(rVariant), std::string());
|
||||
|
||||
unsigned int rFloatVariant = p2->indexOfVariant("c172r-floats");
|
||||
SG_VERIFY(rFloatVariant > 0);
|
||||
SG_CHECK_EQUAL(p2->parentIdForVariant(rFloatVariant), std::string("c172r"));
|
||||
|
||||
string_list primaries = {"c172p", "c172r"};
|
||||
SG_VERIFY(p2->primaryVariants() == primaries);
|
||||
|
||||
// test filtering / searching too
|
||||
string_set tags(p2->tags());
|
||||
|
||||
39
simgear/package/Delegate.cxx
Normal file
39
simgear/package/Delegate.cxx
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2017 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 <simgear/package/Delegate.hxx>
|
||||
#include <simgear/package/Install.hxx>
|
||||
#include <simgear/package/Package.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace pkg
|
||||
{
|
||||
|
||||
void Delegate::installStatusChanged(InstallRef aInstall, StatusCode aReason)
|
||||
{
|
||||
}
|
||||
|
||||
void Delegate::dataForThumbnail(const std::string& aThumbnailUrl,
|
||||
size_t lenth, const uint8_t* bytes)
|
||||
{
|
||||
}
|
||||
|
||||
} // of namespace pkg
|
||||
} // of namespace simgear
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef SG_PACKAGE_DELEGATE_HXX
|
||||
#define SG_PACKAGE_DELEGATE_HXX
|
||||
|
||||
#include <string>
|
||||
#include <simgear/misc/stdint.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
@@ -52,6 +53,7 @@ public:
|
||||
FAIL_FILESYSTEM, ///< unknown filesystem error occurred
|
||||
FAIL_VERSION, ///< version check mismatch
|
||||
FAIL_NOT_FOUND, ///< package URL returned a 404
|
||||
FAIL_HTTP_FORBIDDEN, ///< URL returned a 403. Marked specially to catch rate-limiting
|
||||
STATUS_REFRESHED,
|
||||
USER_CANCELLED
|
||||
} StatusCode;
|
||||
@@ -76,9 +78,15 @@ public:
|
||||
* Notification when catalogs/packages are added or removed
|
||||
*/
|
||||
virtual void availablePackagesChanged() {}
|
||||
|
||||
/**
|
||||
* More general purpose notification when install is queued / cancelled / started
|
||||
* stopped. Reason value is only in certain cases.
|
||||
*/
|
||||
virtual void installStatusChanged(InstallRef aInstall, StatusCode aReason);
|
||||
|
||||
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
|
||||
size_t lenth, const uint8_t* bytes) {}
|
||||
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
|
||||
size_t lenth, const uint8_t* bytes);
|
||||
};
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
#include <simgear/package/Install.hxx>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
@@ -152,6 +153,11 @@ protected:
|
||||
return;
|
||||
}
|
||||
|
||||
// disable caching on the owner's path, otherwise the upcoming
|
||||
// delete & rename confuse everything
|
||||
m_owner->m_path.set_cached(false);
|
||||
m_extractPath.set_cached(false);
|
||||
|
||||
if (m_owner->path().exists()) {
|
||||
Dir destDir(m_owner->path());
|
||||
destDir.remove(true /* recursive */);
|
||||
@@ -450,33 +456,6 @@ void Install::cancelDownload()
|
||||
m_package->catalog()->root()->cancelDownload(this);
|
||||
}
|
||||
|
||||
struct PathAppender
|
||||
{
|
||||
PathAppender(const SGPath& p) : m_path(p) {}
|
||||
|
||||
SGPath operator()(const std::string& s) const
|
||||
{
|
||||
SGPath p(m_path);
|
||||
p.append(s);
|
||||
return p;
|
||||
}
|
||||
|
||||
SGPath m_path;
|
||||
};
|
||||
|
||||
PathList Install::thumbnailPaths() const
|
||||
{
|
||||
const string_list& thumbs(m_package->thumbnails());
|
||||
PathList result;
|
||||
if (thumbs.empty())
|
||||
return result;
|
||||
|
||||
std::transform(thumbs.begin(), thumbs.end(),
|
||||
std::back_inserter(result),
|
||||
PathAppender(m_path));
|
||||
return result;
|
||||
}
|
||||
|
||||
SGPath Install::primarySetPath() const
|
||||
{
|
||||
SGPath setPath(m_path);
|
||||
|
||||
@@ -98,13 +98,6 @@ public:
|
||||
*/
|
||||
void cancelDownload();
|
||||
|
||||
/**
|
||||
* return the thumbnails associated with this install, but as locations
|
||||
* on the file system, not URLs. It is assumed the order of thumbnails
|
||||
* is consistent with the URLs returned from Package::thumbnailUrls()
|
||||
*/
|
||||
PathList thumbnailPaths() const;
|
||||
|
||||
/**
|
||||
* Set the handler to be called when the installation successfully
|
||||
* completes.
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <simgear/package/Package.hxx>
|
||||
|
||||
#include <cassert>
|
||||
@@ -266,6 +268,11 @@ string_set Package::tags() const
|
||||
{
|
||||
return m_tags;
|
||||
}
|
||||
|
||||
bool Package::hasTag(const std::string& tag) const
|
||||
{
|
||||
return m_tags.find(tag) != m_tags.end();
|
||||
}
|
||||
|
||||
SGPropertyNode* Package::properties() const
|
||||
{
|
||||
@@ -274,28 +281,12 @@ SGPropertyNode* Package::properties() const
|
||||
|
||||
string_list Package::thumbnailUrls() const
|
||||
{
|
||||
string_list r;
|
||||
if (!m_props) {
|
||||
return r;
|
||||
string_list urls;
|
||||
const Thumbnail& thumb(thumbnailForVariant(0));
|
||||
if (!thumb.url.empty()) {
|
||||
urls.push_back(thumb.url);
|
||||
}
|
||||
|
||||
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("thumbnail")) {
|
||||
r.push_back(dl->getStringValue());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
string_list Package::thumbnails() const
|
||||
{
|
||||
string_list r;
|
||||
if (!m_props) {
|
||||
return r;
|
||||
}
|
||||
|
||||
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("thumbnail-path")) {
|
||||
r.push_back(dl->getStringValue());
|
||||
}
|
||||
return r;
|
||||
return urls;
|
||||
}
|
||||
|
||||
string_list Package::downloadUrls() const
|
||||
@@ -305,7 +296,7 @@ string_list Package::downloadUrls() const
|
||||
return r;
|
||||
}
|
||||
|
||||
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("url")) {
|
||||
for (auto dl : m_props->getChildren("url")) {
|
||||
r.push_back(dl->getStringValue());
|
||||
}
|
||||
return r;
|
||||
@@ -426,7 +417,49 @@ SGPropertyNode_ptr Package::propsForVariant(const unsigned int vIndex, const cha
|
||||
return m_props;
|
||||
}
|
||||
|
||||
throw sg_exception("Unknow variant in package " + id());
|
||||
throw sg_exception("Unknown variant in package " + id());
|
||||
}
|
||||
|
||||
std::string Package::parentIdForVariant(unsigned int variantIndex) const
|
||||
{
|
||||
const std::string parentId = propsForVariant(variantIndex)->getStringValue("variant-of");
|
||||
if ((variantIndex == 0) || (parentId == "_package_")) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
if (parentId.empty()) {
|
||||
// this is a variant without a variant-of, so assume its parent is
|
||||
// the first primary
|
||||
return m_variants.front();
|
||||
}
|
||||
|
||||
assert(indexOfVariant(parentId) >= 0);
|
||||
return parentId;
|
||||
}
|
||||
|
||||
string_list Package::primaryVariants() const
|
||||
{
|
||||
string_list result;
|
||||
for (unsigned int v = 0; v < m_variants.size(); ++v) {
|
||||
const auto pr = parentIdForVariant(v);
|
||||
if (pr.empty()) {
|
||||
result.push_back(m_variants.at(v));
|
||||
}
|
||||
}
|
||||
assert(!result.empty());
|
||||
assert(result.front() == id());
|
||||
return result;
|
||||
}
|
||||
|
||||
Package::Thumbnail Package::thumbnailForVariant(unsigned int vIndex) const
|
||||
{
|
||||
SGPropertyNode_ptr var = propsForVariant(vIndex);
|
||||
// allow for variants without distinct thumbnails
|
||||
if (!var->hasChild("thumbnail") || !var->hasChild("thumbnail-path")) {
|
||||
var = m_props;
|
||||
}
|
||||
|
||||
return {var->getStringValue("thumbnail"), var->getStringValue("thumbnail-path")};
|
||||
}
|
||||
|
||||
Package::PreviewVec Package::previewsForVariant(unsigned int vIndex) const
|
||||
|
||||
@@ -32,7 +32,7 @@ typedef std::set<std::string> string_set;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
|
||||
namespace pkg
|
||||
{
|
||||
|
||||
@@ -40,13 +40,13 @@ namespace pkg
|
||||
class Install;
|
||||
class Catalog;
|
||||
class Package;
|
||||
|
||||
|
||||
typedef SGSharedPtr<Package> PackageRef;
|
||||
typedef SGSharedPtr<Catalog> CatalogRef;
|
||||
typedef SGSharedPtr<Install> InstallRef;
|
||||
|
||||
typedef std::vector<PackageRef> PackageList;
|
||||
|
||||
|
||||
class Package : public SGReferenced
|
||||
{
|
||||
public:
|
||||
@@ -62,15 +62,25 @@ public:
|
||||
existingInstall(const InstallCallback& cb = InstallCallback()) const;
|
||||
|
||||
bool isInstalled() const;
|
||||
|
||||
|
||||
/**
|
||||
* package ID
|
||||
*/
|
||||
std::string id() const;
|
||||
|
||||
/**
|
||||
* Variant IDs. Note the primary ID will always be included as
|
||||
* variants()[0], to simplify enumerating all variants
|
||||
* Variant IDs
|
||||
*/
|
||||
string_list variants() const;
|
||||
|
||||
/**
|
||||
* All variants without a parent, i.e top-level variants in this package.
|
||||
* Often this is a single-element list matching id() above, but when
|
||||
* packages contain multiple primary aircraft, this will have multiple
|
||||
* elements.
|
||||
*/
|
||||
string_list primaryVariants() const;
|
||||
|
||||
/**
|
||||
* Fully-qualified ID, including our catalog'd ID
|
||||
*/
|
||||
@@ -89,8 +99,10 @@ public:
|
||||
/**
|
||||
* human-readable name - note this is probably not localised,
|
||||
* although this is not ruled out for the future.
|
||||
*
|
||||
* Deprecated - please use nameForVariant
|
||||
*/
|
||||
std::string name() const;
|
||||
SG_DEPRECATED(std::string name() const);
|
||||
|
||||
/**
|
||||
* Human readable name of a variant
|
||||
@@ -101,43 +113,55 @@ public:
|
||||
|
||||
/**
|
||||
* syntactic sugar to get the localised description
|
||||
*
|
||||
* Deprecated - please use getLocalisedProp to get the variant-specific
|
||||
* description.
|
||||
*/
|
||||
std::string description() const;
|
||||
|
||||
SG_DEPRECATED(std::string description() const);
|
||||
|
||||
/**
|
||||
* access the raw property data in the package
|
||||
*/
|
||||
SGPropertyNode* properties() const;
|
||||
|
||||
|
||||
/**
|
||||
* hex-encoded MD5 sum of the download files
|
||||
*/
|
||||
std::string md5() const;
|
||||
|
||||
|
||||
std::string getLocalisedProp(const std::string& aName, const unsigned int vIndex = 0) const;
|
||||
|
||||
unsigned int revision() const;
|
||||
|
||||
|
||||
size_t fileSizeBytes() const;
|
||||
|
||||
|
||||
CatalogRef catalog() const
|
||||
{ return m_catalog; }
|
||||
|
||||
|
||||
bool matches(const SGPropertyNode* aFilter) const;
|
||||
|
||||
string_set tags() const;
|
||||
|
||||
/**
|
||||
* @brief hasTag - efficently check if a tag is defined or not
|
||||
* @param tag
|
||||
* @return
|
||||
*/
|
||||
bool hasTag(const std::string& tag) const;
|
||||
|
||||
/**
|
||||
* download URLs for the package
|
||||
*/
|
||||
string_list downloadUrls() const;
|
||||
|
||||
string_list thumbnailUrls() const;
|
||||
|
||||
/**
|
||||
* thumbnail file paths within the package on disk
|
||||
*/
|
||||
string_list thumbnails() const;
|
||||
|
||||
struct Thumbnail
|
||||
{
|
||||
std::string url;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
Thumbnail thumbnailForVariant(unsigned int vIndex) const;
|
||||
|
||||
/**
|
||||
* information about a preview image
|
||||
@@ -167,7 +191,7 @@ public:
|
||||
* retrieve all the thumbnails for a variant
|
||||
*/
|
||||
PreviewVec previewsForVariant(unsigned int vIndex) const;
|
||||
|
||||
|
||||
/**
|
||||
* Packages we depend upon.
|
||||
* If the dependency list cannot be satisifed for some reason,
|
||||
@@ -180,14 +204,23 @@ public:
|
||||
* same as the primary ID, depending on the aircraft author
|
||||
*/
|
||||
std::string dirName() const;
|
||||
|
||||
/**
|
||||
* Return the parent variant of a variant. This will be the emtpy string if
|
||||
* the variant is primary (top-level), otherwise the local (non-qualified)
|
||||
* ID. This allows establishing a heirarchy of variants within the package.
|
||||
* Note at present most code assumes a maxiumum two-level deep heirarchy
|
||||
* (parents and children)
|
||||
*/
|
||||
std::string parentIdForVariant(unsigned int variantIndex) const;
|
||||
private:
|
||||
SGPath pathOnDisk() const;
|
||||
|
||||
friend class Catalog;
|
||||
friend class Root;
|
||||
|
||||
|
||||
Package(const SGPropertyNode* aProps, CatalogRef aCatalog);
|
||||
|
||||
|
||||
void initWithProps(const SGPropertyNode* aProps);
|
||||
|
||||
void updateFromProps(const SGPropertyNode* aProps);
|
||||
@@ -203,7 +236,7 @@ private:
|
||||
string_set m_tags;
|
||||
CatalogRef m_catalog;
|
||||
string_list m_variants;
|
||||
|
||||
|
||||
mutable function_list<InstallCallback> _install_cb;
|
||||
};
|
||||
|
||||
@@ -215,4 +248,3 @@ private:
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_PACKAGE_PACKAGE_HXX
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <simgear/package/Root.hxx>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
@@ -28,27 +30,50 @@
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/misc/sg_hash.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/package/Package.hxx>
|
||||
#include <simgear/package/Install.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
const int SECONDS_PER_DAY = 24 * 60 * 60;
|
||||
|
||||
namespace simgear {
|
||||
|
||||
namespace {
|
||||
std::string hashForUrl(const std::string& d)
|
||||
{
|
||||
sha1nfo info;
|
||||
sha1_init(&info);
|
||||
sha1_write(&info, d.data(), d.size());
|
||||
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
|
||||
}
|
||||
} // of anonymous namespace
|
||||
|
||||
namespace pkg {
|
||||
|
||||
typedef std::map<std::string, CatalogRef> CatalogDict;
|
||||
typedef std::vector<Delegate*> DelegateVec;
|
||||
typedef std::map<std::string, std::string> MemThumbnailCache;
|
||||
typedef std::deque<std::string> StringDeque;
|
||||
|
||||
class Root::ThumbnailDownloader : public HTTP::Request
|
||||
{
|
||||
public:
|
||||
ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
|
||||
HTTP::Request(aUrl),
|
||||
m_owner(aOwner)
|
||||
ThumbnailDownloader(Root::RootPrivate* aOwner,
|
||||
const std::string& aUrl, const std::string& aRealUrl = std::string()) :
|
||||
HTTP::Request(aUrl),
|
||||
m_owner(aOwner),
|
||||
m_realUrl(aRealUrl)
|
||||
{
|
||||
if (m_realUrl.empty()) {
|
||||
m_realUrl = aUrl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string realUrl() const
|
||||
{
|
||||
return m_realUrl;
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -62,6 +87,7 @@ protected:
|
||||
private:
|
||||
Root::RootPrivate* m_owner;
|
||||
std::string m_buffer;
|
||||
std::string m_realUrl;
|
||||
};
|
||||
|
||||
class Root::RootPrivate
|
||||
@@ -69,69 +95,90 @@ class Root::RootPrivate
|
||||
public:
|
||||
RootPrivate() :
|
||||
http(NULL),
|
||||
maxAgeSeconds(60 * 60 * 24)
|
||||
maxAgeSeconds(SECONDS_PER_DAY)
|
||||
{
|
||||
}
|
||||
|
||||
void fireStatusChange(InstallRef install, Delegate::StatusCode status)
|
||||
{
|
||||
for (auto d : delegates) {
|
||||
d->installStatusChanged(install, status);
|
||||
}
|
||||
}
|
||||
|
||||
void fireStartInstall(InstallRef install)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->startInstall(install);
|
||||
for (auto d : delegates) {
|
||||
d->startInstall(install);
|
||||
d->installStatusChanged(install, Delegate::STATUS_IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void fireInstallProgress(InstallRef install,
|
||||
unsigned int aBytes, unsigned int aTotal)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->installProgress(install, aBytes, aTotal);
|
||||
for (auto d : delegates) {
|
||||
d->installProgress(install, aBytes, aTotal);
|
||||
}
|
||||
}
|
||||
|
||||
void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->finishInstall(install, status);
|
||||
for (auto d : delegates) {
|
||||
d->finishInstall(install, status);
|
||||
d->installStatusChanged(install, status);
|
||||
}
|
||||
}
|
||||
|
||||
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->catalogRefreshed(catalog, status);
|
||||
for (auto d : delegates) {
|
||||
d->catalogRefreshed(catalog, status);
|
||||
}
|
||||
}
|
||||
|
||||
void firePackagesChanged()
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->availablePackagesChanged();
|
||||
}
|
||||
for (auto d : delegates) {
|
||||
d->availablePackagesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void thumbnailDownloadComplete(HTTP::Request_ptr request,
|
||||
Delegate::StatusCode status, const std::string& bytes)
|
||||
{
|
||||
std::string u(request->url());
|
||||
auto dl = static_cast<Root::ThumbnailDownloader*>(request.get());
|
||||
std::string u = dl->realUrl();
|
||||
if (status == Delegate::STATUS_SUCCESS) {
|
||||
thumbnailCache[u] = bytes;
|
||||
fireDataForThumbnail(u, bytes);
|
||||
thumbnailCache[u].requestPending = false;
|
||||
fireDataForThumbnail(u, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
|
||||
|
||||
// if this was a network load, rather than a re-load from the disk cache,
|
||||
// then persist to disk now.
|
||||
if (strutils::starts_with(request->url(), "http")) {
|
||||
addToPersistentCache(u, bytes);
|
||||
}
|
||||
} else if (status == Delegate::FAIL_HTTP_FORBIDDEN) {
|
||||
// treat this as rate-limiting failure, at least from some mirrors
|
||||
// (eg Ibiblio) and retry up to the max count
|
||||
const int retries = (thumbnailCache[u].retryCount++);
|
||||
if (retries < 3) {
|
||||
SG_LOG(SG_IO, SG_INFO, "Download failed for: " << u << ", will retry");
|
||||
thumbnailCache[u].requestPending = true;
|
||||
pendingThumbnails.push_back(u);
|
||||
}
|
||||
} else {
|
||||
// any other failure.
|
||||
thumbnailCache[u].requestPending = false;
|
||||
}
|
||||
|
||||
downloadNextPendingThumbnail();
|
||||
}
|
||||
|
||||
void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
|
||||
void fireDataForThumbnail(const std::string& aUrl, const uint8_t* bytes, size_t size)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.data());
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->dataForThumbnail(aUrl, bytes.size(), data);
|
||||
for (auto d : delegates) {
|
||||
d->dataForThumbnail(aUrl, size, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +202,78 @@ public:
|
||||
|
||||
void fireFinishUninstall(PackageRef pkg)
|
||||
{
|
||||
std::for_each(delegates.begin(), delegates.end(),
|
||||
[pkg](Delegate* d) {d->finishUninstall(pkg);});
|
||||
for (auto d : delegates) {
|
||||
d->finishUninstall(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addToPersistentCache(const std::string& url, const std::string& imageBytes)
|
||||
{
|
||||
std::string hash = hashForUrl(url);
|
||||
// append the correct file suffix
|
||||
auto pos = url.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
SGPath cachePath = path / "ThumbnailCache" / (hash + url.substr(pos));
|
||||
sg_ofstream fstream(cachePath, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
fstream.write(imageBytes.data(), imageBytes.size());
|
||||
fstream.close();
|
||||
}
|
||||
|
||||
bool checkPersistentCache(const std::string& url)
|
||||
{
|
||||
std::string hash = hashForUrl(url);
|
||||
// append the correct file suffix
|
||||
auto pos = url.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SGPath cachePath = path / "ThumbnailCache" / (hash + url.substr(pos));
|
||||
if (!cachePath.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check age, if it's too old, expire and download again
|
||||
int age = time(nullptr) - cachePath.modTime();
|
||||
if (age > SECONDS_PER_DAY * 7) { // cache for seven days
|
||||
SG_LOG(SG_IO, SG_INFO, "expiring old cached thumbnail " << url);
|
||||
cachePath.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
queueLoadFromPersistentCache(url, cachePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void queueLoadFromPersistentCache(const std::string& url, const SGPath& path)
|
||||
{
|
||||
assert(path.exists());
|
||||
|
||||
auto it = thumbnailCache.find(url);
|
||||
if (it == thumbnailCache.end()) {
|
||||
ThumbnailCacheEntry entry;
|
||||
entry.pathOnDisk = path;
|
||||
it = thumbnailCache.insert(it, std::make_pair(url, entry));
|
||||
} else {
|
||||
assert(it->second.pathOnDisk == path);
|
||||
}
|
||||
|
||||
if (it->second.requestPending) {
|
||||
return; // all done
|
||||
}
|
||||
|
||||
it->second.requestPending = true;
|
||||
auto dl = new Root::ThumbnailDownloader(this, path.fileUrl(), url);
|
||||
if (http) {
|
||||
http->makeRequest(dl);
|
||||
} else {
|
||||
httpPendingRequests.push_back(dl);
|
||||
}
|
||||
}
|
||||
|
||||
DelegateVec delegates;
|
||||
|
||||
SGPath path;
|
||||
@@ -176,7 +291,15 @@ public:
|
||||
|
||||
HTTP::Request_ptr thumbnailDownloadRequest;
|
||||
StringDeque pendingThumbnails;
|
||||
MemThumbnailCache thumbnailCache;
|
||||
|
||||
struct ThumbnailCacheEntry
|
||||
{
|
||||
int retryCount = 0;
|
||||
bool requestPending = false;
|
||||
SGPath pathOnDisk;
|
||||
};
|
||||
|
||||
std::map<std::string, ThumbnailCacheEntry> thumbnailCache;
|
||||
|
||||
typedef std::map<PackageRef, InstallRef> InstallCache;
|
||||
InstallCache m_installs;
|
||||
@@ -185,16 +308,19 @@ public:
|
||||
|
||||
void Root::ThumbnailDownloader::onDone()
|
||||
{
|
||||
if (simgear::strutils::starts_with(url(), "file://")) {
|
||||
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseCode() != 200) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
|
||||
m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
|
||||
auto status = (responseCode() == 403) ? Delegate::FAIL_HTTP_FORBIDDEN : Delegate::FAIL_DOWNLOAD;
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "thumbnail download failure: " << url() << " with reason " << responseCode());
|
||||
m_owner->thumbnailDownloadComplete(this, status, std::string());
|
||||
return;
|
||||
}
|
||||
|
||||
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
|
||||
//time(&m_owner->m_retrievedTime);
|
||||
//m_owner->writeTimestamp();
|
||||
//m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
|
||||
}
|
||||
|
||||
SGPath Root::path() const
|
||||
@@ -258,10 +384,14 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
|
||||
Dir dir(aPath);
|
||||
if (!dir.exists()) {
|
||||
dir.create(0755);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
|
||||
Dir thumbsCacheDir(aPath / "ThumbnailCache");
|
||||
if (!thumbsCacheDir.exists()) {
|
||||
thumbsCacheDir.create(0755);
|
||||
}
|
||||
|
||||
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
|
||||
CatalogRef cat = Catalog::createFromPath(this, c);
|
||||
if (cat) {
|
||||
if (cat->status() == Delegate::STATUS_SUCCESS) {
|
||||
@@ -444,7 +574,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
}
|
||||
|
||||
PackageList deps = aInstall->package()->dependencies();
|
||||
BOOST_FOREACH(Package* dep, deps) {
|
||||
for (Package* dep : deps) {
|
||||
// will internally schedule for update if required
|
||||
// hence be careful, this method is re-entered in here!
|
||||
dep->install();
|
||||
@@ -453,6 +583,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
bool wasEmpty = d->updateDeque.empty();
|
||||
d->updateDeque.push_back(aInstall);
|
||||
|
||||
d->fireStatusChange(aInstall, Delegate::STATUS_IN_PROGRESS);
|
||||
if (wasEmpty) {
|
||||
aInstall->startUpdate();
|
||||
}
|
||||
@@ -460,8 +591,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
|
||||
bool Root::isInstallQueued(InstallRef aInstall) const
|
||||
{
|
||||
RootPrivate::UpdateDeque::const_iterator it =
|
||||
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
return (it != d->updateDeque.end());
|
||||
}
|
||||
|
||||
@@ -477,7 +607,7 @@ void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned in
|
||||
|
||||
void Root::startNext(InstallRef aCurrent)
|
||||
{
|
||||
if (d->updateDeque.front() != aCurrent) {
|
||||
if (d->updateDeque.empty() || (d->updateDeque.front() != aCurrent)) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
|
||||
} else {
|
||||
d->updateDeque.pop_front();
|
||||
@@ -503,11 +633,12 @@ void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
|
||||
|
||||
void Root::cancelDownload(InstallRef aInstall)
|
||||
{
|
||||
RootPrivate::UpdateDeque::iterator it =
|
||||
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
if (it != d->updateDeque.end()) {
|
||||
bool startNext = (aInstall == d->updateDeque.front());
|
||||
d->updateDeque.erase(it);
|
||||
d->fireStatusChange(aInstall, Delegate::USER_CANCELLED);
|
||||
|
||||
if (startNext) {
|
||||
if (!d->updateDeque.empty()) {
|
||||
d->updateDeque.front()->startUpdate();
|
||||
@@ -607,17 +738,21 @@ bool Root::removeCatalogById(const std::string& aId)
|
||||
|
||||
void Root::requestThumbnailData(const std::string& aUrl)
|
||||
{
|
||||
MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
|
||||
auto it = d->thumbnailCache.find(aUrl);
|
||||
if (it == d->thumbnailCache.end()) {
|
||||
// insert into cache to mark as pending
|
||||
d->pendingThumbnails.push_front(aUrl);
|
||||
d->thumbnailCache[aUrl] = std::string();
|
||||
d->downloadNextPendingThumbnail();
|
||||
} else if (!it->second.empty()) {
|
||||
// already loaded, fire data synchronously
|
||||
d->fireDataForThumbnail(aUrl, it->second);
|
||||
bool cachedOnDisk = d->checkPersistentCache(aUrl);
|
||||
if (cachedOnDisk) {
|
||||
// checkPersistentCache will insert the entry and schedule
|
||||
} else {
|
||||
d->pendingThumbnails.push_front(aUrl);
|
||||
d->thumbnailCache[aUrl] = RootPrivate::ThumbnailCacheEntry();
|
||||
d->thumbnailCache[aUrl].requestPending = true;
|
||||
d->downloadNextPendingThumbnail();
|
||||
}
|
||||
} else {
|
||||
// in cache but empty data, still fetching
|
||||
if (!it->second.requestPending && it->second.pathOnDisk.exists()) {
|
||||
d->queueLoadFromPersistentCache(aUrl, it->second.pathOnDisk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
<path>thumb-panel.png</path>
|
||||
<url>http://foo.bar.com/thumb-panel.png</url>
|
||||
</preview>
|
||||
|
||||
<thumbnail>http://foo.bar.com/thumb-floats.png</thumbnail>
|
||||
<thumbnail-path>thumb-floats.png</thumbnail-path>
|
||||
</variant>
|
||||
|
||||
<variant>
|
||||
@@ -96,6 +99,8 @@
|
||||
<name>C172 with skis</name>
|
||||
<description>A plane with skis</description>
|
||||
|
||||
<variant-of>c172p</variant-of>
|
||||
|
||||
<preview>
|
||||
<type>exterior</type>
|
||||
<path>thumb-exterior-skis.png</path>
|
||||
@@ -109,10 +114,33 @@
|
||||
</preview>
|
||||
</variant>
|
||||
|
||||
<variant>
|
||||
<id>c172r</id>
|
||||
<name>C172R</name>
|
||||
<description>Equally good version of the C172</description>
|
||||
<variant-of>_package_</variant-of>
|
||||
<preview>
|
||||
<type>panel</type>
|
||||
<path>thumb-panel.png</path>
|
||||
<url>http://foo.bar.com/thumb-panel.png</url>
|
||||
</preview>
|
||||
</variant>
|
||||
|
||||
<variant>
|
||||
<id>c172r-floats</id>
|
||||
<name>C172R-floats</name>
|
||||
<description>Equally good version of the C172 with floats</description>
|
||||
<variant-of>c172r</variant-of>
|
||||
<preview>
|
||||
<type>panel</type>
|
||||
<path>thumb-panel.png</path>
|
||||
<url>http://foo.bar.com/thumb-panel.png</url>
|
||||
</preview>
|
||||
</variant>
|
||||
|
||||
<md5>ec0e2ffdf98d6a5c05c77445e5447ff5</md5>
|
||||
<url>http://localhost:2000/catalogTest1/c172p.zip</url>
|
||||
|
||||
<!-- legacy thumbnails also supported -->
|
||||
<thumbnail>http://foo.bar.com/thumb-exterior.png</thumbnail>
|
||||
<thumbnail-path>exterior.png</thumbnail-path>
|
||||
</package>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
#include <simgear/package/Package.hxx>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user