Compare commits
159 Commits
next
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a157e50302 | ||
|
|
70e6b6b13f | ||
|
|
c457561472 | ||
|
|
2b5ac6350d | ||
|
|
1c30b29168 | ||
|
|
ce9f476ddb | ||
|
|
2621418d3a | ||
|
|
174b341bef | ||
|
|
df33713069 | ||
|
|
cde75530fe | ||
|
|
a9dddbcddc | ||
|
|
fcd72581a1 | ||
|
|
0a6091eeeb | ||
|
|
d6400e3737 | ||
|
|
71e7050349 | ||
|
|
e6f4936536 | ||
|
|
b2ae7e9e63 | ||
|
|
ee0b22fd52 | ||
|
|
01233ba537 | ||
|
|
ed29d9b75d | ||
|
|
5d4201cdfa | ||
|
|
2ef8c0d27b | ||
|
|
0fbfa3426f | ||
|
|
57b4060eb3 | ||
|
|
84a569913d | ||
|
|
b585df04a5 | ||
|
|
ddba0c6731 | ||
|
|
43d849232b | ||
|
|
651460bbc8 | ||
|
|
81a489d81d | ||
|
|
7e76667af0 | ||
|
|
32f69df774 | ||
|
|
d8d64b2367 | ||
|
|
747d99450b | ||
|
|
b023c7c4f4 | ||
|
|
c19cf094a7 | ||
|
|
279179e88d | ||
|
|
0ddd3e7f2f | ||
|
|
fe96298be5 | ||
|
|
c6351292dd | ||
|
|
e2caad3b0b | ||
|
|
a095ab684c | ||
|
|
fe41a03180 | ||
|
|
332d9dfadb | ||
|
|
c05802a498 | ||
|
|
0970bb1be2 | ||
|
|
c9d83fab6c | ||
|
|
edcce32f24 | ||
|
|
68d265f0e7 | ||
|
|
b985bb5757 | ||
|
|
f030816385 | ||
|
|
6f9f694eff | ||
|
|
a0d7f0e172 | ||
|
|
9f98e438cb | ||
|
|
f029ca7b64 | ||
|
|
ca6c6dd6d3 | ||
|
|
4ba4ea5602 | ||
|
|
ca5f66da9f | ||
|
|
92d053d850 | ||
|
|
001ae80723 | ||
|
|
58d9a6d0b5 | ||
|
|
261316cb45 | ||
|
|
2ad4d2e672 | ||
|
|
1d9fa929fa | ||
|
|
26bb6236a0 | ||
|
|
b2acf5a623 | ||
|
|
bea88bb3a9 | ||
|
|
def2af2bdd | ||
|
|
6f6d705f22 | ||
|
|
e9c33104d3 | ||
|
|
985897f8ba | ||
|
|
4251b28e88 | ||
|
|
76540a211c | ||
|
|
904fc5a7dd | ||
|
|
4aebc159d5 | ||
|
|
f89227fc1a | ||
|
|
f50f383cc0 | ||
|
|
706ab387de | ||
|
|
cddfdb7d1d | ||
|
|
3c64578848 | ||
|
|
e1fe9b45e0 | ||
|
|
8fdc1d306f | ||
|
|
6e0c39bb68 | ||
|
|
f20b416cfe | ||
|
|
47e06b5216 | ||
|
|
bfcdf22705 | ||
|
|
d0db407faa | ||
|
|
d95b1c0441 | ||
|
|
837ba86d57 | ||
|
|
0cb1b463e1 | ||
|
|
8a772c8edd | ||
|
|
03bdad0a10 | ||
|
|
537776e1f8 | ||
|
|
f34a4a304e | ||
|
|
a59c4e2c8b | ||
|
|
46f4967f6e | ||
|
|
9305417207 | ||
|
|
11da8b33f9 | ||
|
|
c7b320eb55 | ||
|
|
72b2eb0ebf | ||
|
|
ec3829addb | ||
|
|
96bafef3f3 | ||
|
|
3ff3bd0a6c | ||
|
|
78d073a0f0 | ||
|
|
d62796c19d | ||
|
|
7ea7ff43fc | ||
|
|
dafd185595 | ||
|
|
1568ed8b97 | ||
|
|
444e2ffb2d | ||
|
|
32ccdaec6f | ||
|
|
bd9f04d980 | ||
|
|
9c530d6978 | ||
|
|
05094510be | ||
|
|
41e43eeba0 | ||
|
|
f95cbd703a | ||
|
|
e39036a635 | ||
|
|
1171d57b72 | ||
|
|
852058150b | ||
|
|
9be955262e | ||
|
|
bfa411e9b7 | ||
|
|
dab015742a | ||
|
|
0ab81d36b9 | ||
|
|
4d905135e8 | ||
|
|
afad224ca0 | ||
|
|
99c159d46e | ||
|
|
733efd08dd | ||
|
|
3753c62783 | ||
|
|
3e804605b7 | ||
|
|
35b1d321fe | ||
|
|
6ab7f68f4b | ||
|
|
b2e149a737 | ||
|
|
e28c4fa5ca | ||
|
|
61dc19f635 | ||
|
|
60634bc445 | ||
|
|
b3e93eaf6e | ||
|
|
36dca92c2b | ||
|
|
5a1ed52d7c | ||
|
|
fd191b51ce | ||
|
|
6167159795 | ||
|
|
672afdbc34 | ||
|
|
27e61b3dec | ||
|
|
f1d00c9b40 | ||
|
|
1b00ece8c4 | ||
|
|
d0f24229b2 | ||
|
|
ab8795f6dc | ||
|
|
2fe60c9635 | ||
|
|
27a3ee3bce | ||
|
|
0721db3acd | ||
|
|
24b58cbe21 | ||
|
|
5b3274e688 | ||
|
|
fcd75cfae5 | ||
|
|
6387a1d6d0 | ||
|
|
fc4ce2528b | ||
|
|
18d2bfcd8b | ||
|
|
6738a3aa2b | ||
|
|
a8c1bef0bf | ||
|
|
4faf0ea468 | ||
|
|
80cc09fe90 | ||
|
|
ca8dbb985e |
@@ -49,7 +49,7 @@ set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
|
||||
# read 'version' file into a variable (stripping any newlines or spaces)
|
||||
file(READ version versionFile)
|
||||
file(READ simgear-version versionFile)
|
||||
string(STRIP ${versionFile} SIMGEAR_VERSION)
|
||||
|
||||
project(SimGear VERSION ${SIMGEAR_VERSION} LANGUAGES C CXX)
|
||||
@@ -278,7 +278,15 @@ else()
|
||||
endif()
|
||||
endif(SIMGEAR_HEADLESS)
|
||||
|
||||
find_package(ZLIB 1.2.4 REQUIRED)
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||
# As of 2020-08-01, OpenBSD's system zlib is slightly old, but it's usable
|
||||
# with a workaround in simgear/io/iostreams/gzfstream.cxx.
|
||||
find_package(ZLIB 1.2.3 REQUIRED)
|
||||
else()
|
||||
find_package(ZLIB 1.2.4 REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(LibLZMA REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
if (SYSTEM_EXPAT)
|
||||
@@ -433,6 +441,10 @@ if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
check_cxx_source_compiles(
|
||||
"int main() { unsigned mValue; return __sync_add_and_fetch(&mValue, 1); }"
|
||||
GCC_ATOMIC_BUILTINS_FOUND)
|
||||
|
||||
# override CMake default RelWithDebInfo flags.
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
|
||||
endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
if (CLANG)
|
||||
|
||||
124
CMakeModules/FindLibLZMA.cmake
Normal file
124
CMakeModules/FindLibLZMA.cmake
Normal file
@@ -0,0 +1,124 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindLibLZMA
|
||||
-----------
|
||||
|
||||
Find LZMA compression algorithm headers and library.
|
||||
|
||||
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module defines :prop_tgt:`IMPORTED` target ``LibLZMA::LibLZMA``, if
|
||||
liblzma has been found.
|
||||
|
||||
Result variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module will set the following variables in your project:
|
||||
|
||||
``LIBLZMA_FOUND``
|
||||
True if liblzma headers and library were found.
|
||||
``LIBLZMA_INCLUDE_DIRS``
|
||||
Directory where liblzma headers are located.
|
||||
``LIBLZMA_LIBRARIES``
|
||||
Lzma libraries to link against.
|
||||
``LIBLZMA_HAS_AUTO_DECODER``
|
||||
True if lzma_auto_decoder() is found (required).
|
||||
``LIBLZMA_HAS_EASY_ENCODER``
|
||||
True if lzma_easy_encoder() is found (required).
|
||||
``LIBLZMA_HAS_LZMA_PRESET``
|
||||
True if lzma_lzma_preset() is found (required).
|
||||
``LIBLZMA_VERSION_MAJOR``
|
||||
The major version of lzma
|
||||
``LIBLZMA_VERSION_MINOR``
|
||||
The minor version of lzma
|
||||
``LIBLZMA_VERSION_PATCH``
|
||||
The patch version of lzma
|
||||
``LIBLZMA_VERSION_STRING``
|
||||
version number as a string (ex: "5.0.3")
|
||||
#]=======================================================================]
|
||||
|
||||
find_path(LIBLZMA_INCLUDE_DIR lzma.h )
|
||||
if(NOT LIBLZMA_LIBRARY)
|
||||
find_library(LIBLZMA_LIBRARY_RELEASE NAMES lzma liblzma NAMES_PER_DIR PATH_SUFFIXES lib)
|
||||
find_library(LIBLZMA_LIBRARY_DEBUG NAMES lzmad liblzmad NAMES_PER_DIR PATH_SUFFIXES lib)
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(LIBLZMA)
|
||||
else()
|
||||
file(TO_CMAKE_PATH "${LIBLZMA_LIBRARY}" LIBLZMA_LIBRARY)
|
||||
endif()
|
||||
|
||||
if(LIBLZMA_INCLUDE_DIR AND EXISTS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h")
|
||||
file(STRINGS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h" LIBLZMA_HEADER_CONTENTS REGEX "#define LZMA_VERSION_[A-Z]+ [0-9]+")
|
||||
|
||||
string(REGEX REPLACE ".*#define LZMA_VERSION_MAJOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MAJOR "${LIBLZMA_HEADER_CONTENTS}")
|
||||
string(REGEX REPLACE ".*#define LZMA_VERSION_MINOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MINOR "${LIBLZMA_HEADER_CONTENTS}")
|
||||
string(REGEX REPLACE ".*#define LZMA_VERSION_PATCH ([0-9]+).*" "\\1" LIBLZMA_VERSION_PATCH "${LIBLZMA_HEADER_CONTENTS}")
|
||||
|
||||
set(LIBLZMA_VERSION_STRING "${LIBLZMA_VERSION_MAJOR}.${LIBLZMA_VERSION_MINOR}.${LIBLZMA_VERSION_PATCH}")
|
||||
unset(LIBLZMA_HEADER_CONTENTS)
|
||||
endif()
|
||||
|
||||
# We're using new code known now as XZ, even library still been called LZMA
|
||||
# it can be found in http://tukaani.org/xz/
|
||||
# Avoid using old codebase
|
||||
if (LIBLZMA_LIBRARY)
|
||||
include(CheckLibraryExists)
|
||||
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
|
||||
set(CMAKE_REQUIRED_QUIET ${LibLZMA_FIND_QUIETLY})
|
||||
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
|
||||
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY})
|
||||
elseif(LIBLZMA_LIBRARY_RELEASE)
|
||||
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_RELEASE})
|
||||
elseif(LIBLZMA_LIBRARY_DEBUG)
|
||||
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_DEBUG})
|
||||
endif()
|
||||
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_auto_decoder "" LIBLZMA_HAS_AUTO_DECODER)
|
||||
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_easy_encoder "" LIBLZMA_HAS_EASY_ENCODER)
|
||||
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_lzma_preset "" LIBLZMA_HAS_LZMA_PRESET)
|
||||
unset(LIBLZMA_LIBRARY_check)
|
||||
set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibLZMA REQUIRED_VARS LIBLZMA_LIBRARY
|
||||
LIBLZMA_INCLUDE_DIR
|
||||
LIBLZMA_HAS_AUTO_DECODER
|
||||
LIBLZMA_HAS_EASY_ENCODER
|
||||
LIBLZMA_HAS_LZMA_PRESET
|
||||
VERSION_VAR LIBLZMA_VERSION_STRING
|
||||
)
|
||||
mark_as_advanced( LIBLZMA_INCLUDE_DIR LIBLZMA_LIBRARY )
|
||||
|
||||
if (LIBLZMA_FOUND)
|
||||
set(LIBLZMA_LIBRARIES ${LIBLZMA_LIBRARY})
|
||||
set(LIBLZMA_INCLUDE_DIRS ${LIBLZMA_INCLUDE_DIR})
|
||||
if(NOT TARGET LibLZMA::LibLZMA)
|
||||
add_library(LibLZMA::LibLZMA UNKNOWN IMPORTED)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${LIBLZMA_INCLUDE_DIR}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C)
|
||||
|
||||
if(LIBLZMA_LIBRARY_RELEASE)
|
||||
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${LIBLZMA_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
|
||||
if(LIBLZMA_LIBRARY_DEBUG)
|
||||
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${LIBLZMA_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
|
||||
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
IMPORTED_LOCATION "${LIBLZMA_LIBRARY}")
|
||||
endif()
|
||||
endif()
|
||||
endif ()
|
||||
@@ -1,6 +1,7 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
find_dependency(ZLIB)
|
||||
find_dependency(LibLZMA)
|
||||
find_dependency(Threads)
|
||||
|
||||
# OSG
|
||||
|
||||
1
simgear-version
Normal file
1
simgear-version
Normal file
@@ -0,0 +1 @@
|
||||
2020.3.11
|
||||
@@ -167,7 +167,10 @@ target_link_libraries(SimGearCore PRIVATE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${COCOA_LIBRARY}
|
||||
${CURL_LIBRARIES}
|
||||
${WINSOCK_LIBRARY})
|
||||
${WINSOCK_LIBRARY}
|
||||
${SHLWAPI_LIBRARY}
|
||||
LibLZMA::LibLZMA
|
||||
)
|
||||
|
||||
if(SYSTEM_EXPAT)
|
||||
target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES})
|
||||
|
||||
@@ -691,6 +691,10 @@ namespace canvas
|
||||
{
|
||||
_sampling_dirty = true;
|
||||
}
|
||||
else if( name == "anisotropy" )
|
||||
{
|
||||
_texture.setMaxAnisotropy( node->getFloatValue() );
|
||||
}
|
||||
else if( name == "additive-blend" )
|
||||
{
|
||||
_texture.useAdditiveBlend( node->getBoolValue() );
|
||||
|
||||
@@ -202,6 +202,12 @@ namespace canvas
|
||||
updateSampling();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setMaxAnisotropy(float anis)
|
||||
{
|
||||
texture->setMaxAnisotropy(anis);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setRender(bool render)
|
||||
{
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace canvas
|
||||
int coverage_samples = 0,
|
||||
int color_samples = 0 );
|
||||
|
||||
void setMaxAnisotropy(float anis);
|
||||
|
||||
/**
|
||||
* Enable/Disable updating the texture (If disabled the contents of the
|
||||
* texture remains with the outcome of the last rendering pass)
|
||||
|
||||
@@ -850,7 +850,7 @@ namespace canvas
|
||||
void fillRow(GLubyte* row, GLuint pixel, GLuint width, GLuint pixelBytes)
|
||||
{
|
||||
GLubyte* dst = row;
|
||||
for (int x = 0; x < width; ++x) {
|
||||
for (GLuint x = 0; x < width; ++x) {
|
||||
memcpy(dst, &pixel, pixelBytes);
|
||||
dst += pixelBytes;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace canvas
|
||||
int stretch,
|
||||
uint8_t alignment )
|
||||
{
|
||||
ItemData item_data = {0};
|
||||
ItemData item_data = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
item_data.layout_item = item;
|
||||
item_data.stretch = std::max(0, stretch);
|
||||
|
||||
|
||||
@@ -218,6 +218,9 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
|
||||
#define SG_OBJECT_RANGE_ROUGH 9000.0
|
||||
#define SG_OBJECT_RANGE_DETAILED 1500.0
|
||||
|
||||
/** Minimum expiry time of PagedLOD within the Tile. Overridden by /sim/rendering/plod-minimum-expiry-time-secs **/
|
||||
#define SG_TILE_MIN_EXPIRY 180.0
|
||||
|
||||
/** Radius of scenery tiles in m **/
|
||||
#define SG_TILE_RADIUS 14000.0
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <vector>
|
||||
#include <memory> // for std::unique_ptr
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/debug/LogCallback.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
|
||||
include (SimGearComponent)
|
||||
|
||||
set(HEADERS debug_types.h logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx)
|
||||
set(SOURCES logstream.cxx BufferedLogCallback.cxx)
|
||||
set(HEADERS debug_types.h
|
||||
logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx
|
||||
LogCallback.hxx LogEntry.hxx
|
||||
ErrorReportingCallback.hxx)
|
||||
|
||||
set(SOURCES logstream.cxx BufferedLogCallback.cxx
|
||||
LogCallback.cxx LogEntry.cxx
|
||||
ErrorReportingCallback.cxx
|
||||
)
|
||||
|
||||
simgear_component(debug debug "${SOURCES}" "${HEADERS}")
|
||||
|
||||
120
simgear/debug/ErrorReportingCallback.cxx
Normal file
120
simgear/debug/ErrorReportingCallback.cxx
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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_config.h>
|
||||
|
||||
#include "ErrorReportingCallback.hxx"
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace simgear {
|
||||
|
||||
static ErrorReportCallback static_callback;
|
||||
static ContextCallback static_contextCallback;
|
||||
|
||||
|
||||
void setErrorReportCallback(ErrorReportCallback cb)
|
||||
{
|
||||
static_callback = cb;
|
||||
}
|
||||
|
||||
|
||||
void reportError(const std::string& msg, const std::string& more)
|
||||
{
|
||||
if (!static_callback)
|
||||
return;
|
||||
|
||||
static_callback(msg, more, false);
|
||||
}
|
||||
|
||||
void reportFatalError(const std::string& msg, const std::string& more)
|
||||
{
|
||||
if (!static_callback)
|
||||
return;
|
||||
static_callback(msg, more, true);
|
||||
}
|
||||
|
||||
static FailureCallback static_failureCallback;
|
||||
|
||||
void reportFailure(LoadFailure type, ErrorCode code, const std::string& details, sg_location loc)
|
||||
{
|
||||
if (!static_failureCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
static_failureCallback(type, code, details, loc);
|
||||
}
|
||||
|
||||
void reportFailure(LoadFailure type, ErrorCode code, const std::string& details, const SGPath& path)
|
||||
{
|
||||
if (!static_failureCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
static_failureCallback(type, code, details, sg_location{path});
|
||||
}
|
||||
|
||||
void setFailureCallback(FailureCallback cb)
|
||||
{
|
||||
static_failureCallback = cb;
|
||||
}
|
||||
|
||||
void setErrorContextCallback(ContextCallback cb)
|
||||
{
|
||||
static_contextCallback = cb;
|
||||
}
|
||||
|
||||
ErrorReportContext::ErrorReportContext(const std::string& key, const std::string& value)
|
||||
{
|
||||
add(key, value);
|
||||
}
|
||||
|
||||
void ErrorReportContext::add(const std::string& key, const std::string& value)
|
||||
{
|
||||
if (static_contextCallback) {
|
||||
_keys.push_back(key);
|
||||
static_contextCallback(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorReportContext::ErrorReportContext(const ContextMap& context)
|
||||
{
|
||||
addFromMap(context);
|
||||
}
|
||||
|
||||
void ErrorReportContext::addFromMap(const ContextMap& context)
|
||||
{
|
||||
if (static_contextCallback) {
|
||||
for (const auto& p : context) {
|
||||
_keys.push_back(p.first);
|
||||
static_contextCallback(p.first, p.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ErrorReportContext::~ErrorReportContext()
|
||||
{
|
||||
if (static_contextCallback) {
|
||||
// pop all our keys
|
||||
for (const auto& k : _keys) {
|
||||
static_contextCallback(k, "POP");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
128
simgear/debug/ErrorReportingCallback.hxx
Normal file
128
simgear/debug/ErrorReportingCallback.hxx
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
//forward decls
|
||||
class SGPath;
|
||||
|
||||
namespace simgear {
|
||||
|
||||
void reportError(const std::string& msg, const std::string& more = {});
|
||||
|
||||
void reportFatalError(const std::string& msg, const std::string& more = {});
|
||||
|
||||
using ErrorReportCallback = std::function<void(const std::string& msg, const std::string& more, bool isFatal)>;
|
||||
|
||||
void setErrorReportCallback(ErrorReportCallback cb);
|
||||
|
||||
/** kinds of failures we can report. This is *how* (or why) something failed. Extend
|
||||
as necessary but update the correponsdings string translations if you do. More detail isn't
|
||||
necessariyl useful here: better to provide that in the 'details' string
|
||||
*/
|
||||
enum class LoadFailure {
|
||||
Unknown,
|
||||
NotFound,
|
||||
OutOfMemory,
|
||||
BadHeader,
|
||||
BadData,
|
||||
Misconfigured,
|
||||
IOError, // disk full, permissions error, etc
|
||||
NetworkError
|
||||
};
|
||||
|
||||
/**
|
||||
@brief enum of the operations which can fail. This should be extended as necessary: it maps to
|
||||
translated error messages for the user. This describes what failed, the enum above gives why/how. The
|
||||
combination of what+why should be something at the user level: use details for debug-level information.
|
||||
*/
|
||||
enum class ErrorCode {
|
||||
LoadEffectsShaders,
|
||||
LoadingTexture,
|
||||
XMLModelLoad,
|
||||
ThreeDModelLoad, // AC3D, OBJ, etc
|
||||
BTGLoad,
|
||||
ScenarioLoad,
|
||||
GUIDialog,
|
||||
AudioFX,
|
||||
XMLLoadCommand,
|
||||
AircraftSystems, // autopilot, hydrualics, instruments
|
||||
InputDeviceConfig,
|
||||
AITrafficSchedule,
|
||||
TerraSync
|
||||
};
|
||||
/**
|
||||
@brief Define an error-reporting context value, for the duration of this
|
||||
object's lifetime. The context value will be active for any errors occuring on the same thread, while
|
||||
this object exists.
|
||||
*/
|
||||
class ErrorReportContext
|
||||
{
|
||||
public:
|
||||
ErrorReportContext(const std::string& key, const std::string& value);
|
||||
|
||||
using ContextMap = std::map<std::string, std::string>;
|
||||
|
||||
/**
|
||||
Allow establishing multiple context values in a single operation
|
||||
*/
|
||||
ErrorReportContext(const ContextMap& context = {});
|
||||
|
||||
void add(const std::string& key, const std::string& value);
|
||||
|
||||
/**
|
||||
@brief allowed delayed add of values
|
||||
*/
|
||||
void addFromMap(const ContextMap& context);
|
||||
|
||||
~ErrorReportContext();
|
||||
|
||||
private:
|
||||
std::vector<std::string> _keys;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Report failure to load a resource, so they can be collated for reporting
|
||||
* to the user.
|
||||
*
|
||||
* @param type - the reason for the failure, if it can be determined
|
||||
* @param msg - an informational message about what caused the failure
|
||||
* @param path - path on disk to the resource. In some cases this may be a relative path;
|
||||
* especially in the case of a resource not found, we cannot report a file path.
|
||||
*/
|
||||
|
||||
void reportFailure(LoadFailure type, ErrorCode code, const std::string& detailedMessage = {}, sg_location loc = {});
|
||||
|
||||
/**
|
||||
overload taking a path as the location
|
||||
*/
|
||||
void reportFailure(LoadFailure type, ErrorCode code, const std::string& detailedMessage, const SGPath& p);
|
||||
|
||||
using FailureCallback = std::function<void(LoadFailure type, ErrorCode code, const std::string& details, const sg_location& location)>;
|
||||
|
||||
void setFailureCallback(FailureCallback cb);
|
||||
|
||||
using ContextCallback = std::function<void(const std::string& key, const std::string& value)>;
|
||||
|
||||
void setErrorContextCallback(ContextCallback cb);
|
||||
|
||||
} // namespace simgear
|
||||
116
simgear/debug/LogCallback.cxx
Normal file
116
simgear/debug/LogCallback.cxx
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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_config.h>
|
||||
|
||||
#include "LogCallback.hxx"
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) : m_class(c),
|
||||
m_priority(p)
|
||||
{
|
||||
}
|
||||
|
||||
void LogCallback::operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
// override me
|
||||
}
|
||||
|
||||
bool LogCallback::doProcessEntry(const LogEntry& e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::processEntry(const LogEntry& e)
|
||||
{
|
||||
if (doProcessEntry(e))
|
||||
return; // derived class used the new API
|
||||
|
||||
// call the old API
|
||||
(*this)(e.debugClass, e.debugPriority, e.file, e.line, e.message);
|
||||
}
|
||||
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
if ((c & m_class) != 0 && p >= m_priority)
|
||||
return true;
|
||||
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels(sgDebugClass c, sgDebugPriority p)
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
|
||||
{
|
||||
switch (p) {
|
||||
case SG_DEV_ALERT:
|
||||
case SG_ALERT:
|
||||
return "ALRT";
|
||||
case SG_BULK: return "BULK";
|
||||
case SG_DEBUG: return "DBUG";
|
||||
case SG_MANDATORY_INFO:
|
||||
case SG_INFO:
|
||||
return "INFO";
|
||||
case SG_POPUP: return "POPU";
|
||||
case SG_DEV_WARN:
|
||||
case SG_WARN:
|
||||
return "WARN";
|
||||
|
||||
default: return "UNKN";
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
case SG_HEADLESS: return "headless";
|
||||
case SG_OSG: return "OSG";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
56
simgear/debug/LogCallback.hxx
Normal file
56
simgear/debug/LogCallback.hxx
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "LogEntry.hxx"
|
||||
#include "debug_types.h"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class LogCallback
|
||||
{
|
||||
public:
|
||||
virtual ~LogCallback() = default;
|
||||
|
||||
// newer API: return true if you handled the message, otherwise
|
||||
// the old API will be called
|
||||
virtual bool doProcessEntry(const LogEntry& e);
|
||||
|
||||
// old API, kept for compatability
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage);
|
||||
|
||||
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
void processEntry(const LogEntry& e);
|
||||
|
||||
protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
|
||||
static const char* debugClassToString(sgDebugClass c);
|
||||
static const char* debugPriorityToString(sgDebugPriority p);
|
||||
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
|
||||
} // namespace simgear
|
||||
45
simgear/debug/LogEntry.cxx
Normal file
45
simgear/debug/LogEntry.cxx
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
// 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_config.h>
|
||||
|
||||
#include "LogEntry.hxx"
|
||||
|
||||
#include <cstring> // for strdup
|
||||
|
||||
namespace simgear {
|
||||
|
||||
LogEntry::~LogEntry()
|
||||
{
|
||||
if (freeFilename) {
|
||||
free(const_cast<char*>(file));
|
||||
}
|
||||
}
|
||||
|
||||
LogEntry::LogEntry(const LogEntry& c) : debugClass(c.debugClass),
|
||||
debugPriority(c.debugPriority),
|
||||
originalPriority(c.originalPriority),
|
||||
file(c.file),
|
||||
line(c.line),
|
||||
message(c.message),
|
||||
freeFilename(c.freeFilename)
|
||||
{
|
||||
if (c.freeFilename) {
|
||||
file = strdup(c.file);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
54
simgear/debug/LogEntry.hxx
Normal file
54
simgear/debug/LogEntry.hxx
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "debug_types.h"
|
||||
|
||||
namespace simgear {
|
||||
/**
|
||||
* storage of a single log entry. This is used to pass log entries from
|
||||
* the various threads to the logging thread, and also to store the startup
|
||||
* entries
|
||||
*/
|
||||
class LogEntry
|
||||
{
|
||||
public:
|
||||
LogEntry(sgDebugClass c, sgDebugPriority p,
|
||||
sgDebugPriority op,
|
||||
const char* f, int l, const std::string& msg) : debugClass(c), debugPriority(p), originalPriority(op),
|
||||
file(f), line(l),
|
||||
message(msg)
|
||||
{
|
||||
}
|
||||
|
||||
LogEntry(const LogEntry& c);
|
||||
LogEntry& operator=(const LogEntry& c) = delete;
|
||||
|
||||
~LogEntry();
|
||||
|
||||
const sgDebugClass debugClass;
|
||||
const sgDebugPriority debugPriority;
|
||||
const sgDebugPriority originalPriority;
|
||||
const char* file;
|
||||
const int line;
|
||||
const std::string message;
|
||||
|
||||
bool freeFilename = false; ///< if true, we own, and therefore need to free, the memory pointed to by 'file'
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
#include <osg/Notify>
|
||||
|
||||
using namespace osg;
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
/**
|
||||
* merge OSG output into our logging system, so it gets recorded to file,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
/** \file debug_types.h
|
||||
* Define the various logging classes and priorities
|
||||
*/
|
||||
@@ -52,16 +54,17 @@ typedef enum {
|
||||
* appended, or the priority Nasal reports to compiled code will change.
|
||||
*/
|
||||
typedef enum {
|
||||
SG_BULK = 1, // For frequent messages
|
||||
SG_DEBUG, // Less frequent debug type messages
|
||||
SG_INFO, // Informatory messages
|
||||
SG_WARN, // Possible impending problem
|
||||
SG_ALERT, // Very possible impending problem
|
||||
SG_POPUP, // Severe enough to alert using a pop-up window
|
||||
SG_BULK = 1, // For frequent messages
|
||||
SG_DEBUG, // Less frequent debug type messages
|
||||
SG_INFO, // Informatory messages
|
||||
SG_WARN, // Possible impending problem
|
||||
SG_ALERT, // Very possible impending problem
|
||||
SG_POPUP, // Severe enough to alert using a pop-up window
|
||||
// SG_EXIT, // Problem (no core)
|
||||
// SG_ABORT // Abandon ship (core)
|
||||
|
||||
SG_DEV_WARN, // Warning for developers, translated to other priority
|
||||
SG_DEV_ALERT // Alert for developers, translated
|
||||
} sgDebugPriority;
|
||||
SG_DEV_WARN, // Warning for developers, translated to other priority
|
||||
SG_DEV_ALERT, // Alert for developers, translated
|
||||
|
||||
SG_MANDATORY_INFO // information, but should always be shown
|
||||
} sgDebugPriority;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <simgear/threads/SGThread.hxx>
|
||||
#include <simgear/threads/SGQueue.hxx>
|
||||
|
||||
#include "LogCallback.hxx"
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
@@ -47,86 +48,9 @@
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
m_class(c),
|
||||
m_priority(p)
|
||||
{
|
||||
}
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
|
||||
if ((c & m_class) != 0 && p >= m_priority)
|
||||
return true;
|
||||
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
|
||||
{
|
||||
switch (p) {
|
||||
case SG_ALERT: return "ALRT";
|
||||
case SG_BULK: return "BULK";
|
||||
case SG_DEBUG: return "DBUG";
|
||||
case SG_INFO: return "INFO";
|
||||
case SG_POPUP: return "POPU";
|
||||
case SG_WARN: return "WARN";
|
||||
default: return "UNKN";
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
case SG_HEADLESS: return "headless";
|
||||
case SG_OSG: return "OSG";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class FileLogCallback : public simgear::LogCallback
|
||||
{
|
||||
@@ -139,8 +63,8 @@ public:
|
||||
logTimer.stamp();
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message)
|
||||
void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message) override
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
@@ -196,8 +120,8 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) override
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
//fprintf(stderr, "%s\n", aMessage.c_str());
|
||||
@@ -226,8 +150,8 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) override
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
@@ -242,28 +166,6 @@ public:
|
||||
class logstream::LogStreamPrivate : public SGThread
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* storage of a single log entry. This is used to pass log entries from
|
||||
* the various threads to the logging thread, and also to store the startup
|
||||
* entries
|
||||
*/
|
||||
class LogEntry
|
||||
{
|
||||
public:
|
||||
LogEntry(sgDebugClass c, sgDebugPriority p,
|
||||
const char* f, int l, const std::string& msg) :
|
||||
debugClass(c), debugPriority(p), file(f), line(l),
|
||||
message(msg)
|
||||
{
|
||||
}
|
||||
|
||||
const sgDebugClass debugClass;
|
||||
const sgDebugPriority debugPriority;
|
||||
const char* file;
|
||||
const int line;
|
||||
const std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* RAII object to pause the logging thread if it's running, and restart it.
|
||||
* used to safely make configuration changes.
|
||||
@@ -401,13 +303,20 @@ public:
|
||||
~LogStreamPrivate()
|
||||
{
|
||||
removeCallbacks();
|
||||
|
||||
// house-keeping, avoid leak warnings if we exit before disabling
|
||||
// startup logging
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_lock);
|
||||
clearStartupEntriesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_lock;
|
||||
SGBlockingQueue<LogEntry> m_entries;
|
||||
SGBlockingQueue<simgear::LogEntry> m_entries;
|
||||
|
||||
// log entries posted during startup
|
||||
std::vector<LogEntry> m_startupEntries;
|
||||
std::vector<simgear::LogEntry> m_startupEntries;
|
||||
bool m_startupLogging = false;
|
||||
|
||||
typedef std::vector<simgear::LogCallback*> CallbackVec;
|
||||
@@ -430,6 +339,8 @@ public:
|
||||
// test suite mode.
|
||||
bool m_testMode = false;
|
||||
|
||||
std::vector<std::string> _popupMessages;
|
||||
|
||||
void startLog()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_lock);
|
||||
@@ -447,14 +358,19 @@ public:
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_lock);
|
||||
m_startupLogging = on;
|
||||
m_startupEntries.clear();
|
||||
clearStartupEntriesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void run()
|
||||
void clearStartupEntriesLocked()
|
||||
{
|
||||
m_startupEntries.clear();
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
while (1) {
|
||||
LogEntry entry(m_entries.pop());
|
||||
simgear::LogEntry entry(m_entries.pop());
|
||||
// special marker entry detected, terminate the thread since we are
|
||||
// making a configuration change or quitting the app
|
||||
if ((entry.debugClass == SG_NONE) && entry.file && !strcmp(entry.file, "done")) {
|
||||
@@ -470,8 +386,7 @@ public:
|
||||
}
|
||||
// submit to each installed callback in turn
|
||||
for (simgear::LogCallback* cb : m_callbacks) {
|
||||
(*cb)(entry.debugClass, entry.debugPriority,
|
||||
entry.file, entry.line, entry.message);
|
||||
cb->processEntry(entry);
|
||||
}
|
||||
} // of main thread loop
|
||||
}
|
||||
@@ -486,7 +401,7 @@ public:
|
||||
|
||||
// log a special marker value, which will cause the thread to wakeup,
|
||||
// and then exit
|
||||
log(SG_NONE, SG_ALERT, "done", -1, "");
|
||||
log(SG_NONE, SG_ALERT, "done", -1, "", false);
|
||||
}
|
||||
join();
|
||||
|
||||
@@ -501,9 +416,8 @@ public:
|
||||
|
||||
// we clear startup entries not using this, so always safe to run
|
||||
// this code, container will simply be empty
|
||||
for (auto entry : m_startupEntries) {
|
||||
(*cb)(entry.debugClass, entry.debugPriority,
|
||||
entry.file, entry.line, entry.message);
|
||||
for (const auto& entry : m_startupEntries) {
|
||||
cb->processEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,9 +445,9 @@ public:
|
||||
PauseThread pause(this);
|
||||
m_logPriority = p;
|
||||
m_logClass = c;
|
||||
for (auto cb : m_consoleCallbacks) {
|
||||
cb->setLogLevels(c, p);
|
||||
}
|
||||
for (auto cb : m_consoleCallbacks) {
|
||||
cb->setLogLevels(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
bool would_log( sgDebugClass c, sgDebugPriority p ) const
|
||||
@@ -551,14 +465,17 @@ public:
|
||||
}
|
||||
|
||||
void log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
const char* fileName, int line, const std::string& msg,
|
||||
bool freeFilename)
|
||||
{
|
||||
p = translatePriority(p);
|
||||
auto tp = translatePriority(p);
|
||||
if (!m_fileLine) {
|
||||
/* This prevents output of file:line in StderrLogCallback. */
|
||||
line = -line;
|
||||
}
|
||||
LogEntry entry(c, p, fileName, line, msg);
|
||||
|
||||
simgear::LogEntry entry(c, tp, p, fileName, line, msg);
|
||||
entry.freeFilename = freeFilename;
|
||||
m_entries.push(entry);
|
||||
}
|
||||
|
||||
@@ -589,8 +506,8 @@ logstream::logstream()
|
||||
|
||||
logstream::~logstream()
|
||||
{
|
||||
popup_msgs.clear();
|
||||
d->stop();
|
||||
d.reset();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -625,7 +542,14 @@ void
|
||||
logstream::log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
{
|
||||
d->log(c, p, fileName, line, msg);
|
||||
d->log(c, p, fileName, line, msg, false);
|
||||
}
|
||||
|
||||
void
|
||||
logstream::logCopyingFilename( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
{
|
||||
d->log(c, p, strdup(fileName), line, msg, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -686,17 +610,18 @@ void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName,
|
||||
void
|
||||
logstream::popup( const std::string& msg)
|
||||
{
|
||||
popup_msgs.push_back(msg);
|
||||
std::lock_guard<std::mutex> g(d->m_lock);
|
||||
d->_popupMessages.push_back(msg);
|
||||
}
|
||||
|
||||
std::string
|
||||
logstream::get_popup()
|
||||
{
|
||||
std::string rv = "";
|
||||
if (!popup_msgs.empty())
|
||||
{
|
||||
rv = popup_msgs.front();
|
||||
popup_msgs.erase(popup_msgs.begin());
|
||||
std::string rv;
|
||||
std::lock_guard<std::mutex> g(d->m_lock);
|
||||
if (!d->_popupMessages.empty()) {
|
||||
rv = d->_popupMessages.front();
|
||||
d->_popupMessages.erase(d->_popupMessages.begin());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -704,7 +629,8 @@ logstream::get_popup()
|
||||
bool
|
||||
logstream::has_popup()
|
||||
{
|
||||
return (popup_msgs.size() > 0) ? true : false;
|
||||
std::lock_guard<std::mutex> g(d->m_lock);
|
||||
return !d->_popupMessages.empty();
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -737,6 +663,16 @@ logstream::set_log_classes( sgDebugClass c)
|
||||
d->setLogLevels(c, d->m_logPriority);
|
||||
}
|
||||
|
||||
sgDebugPriority logstream::priorityFromString(const std::string& s)
|
||||
{
|
||||
if (s == "bulk") return SG_BULK;
|
||||
if (s == "debug") return SG_DEBUG;
|
||||
if (s == "info") return SG_INFO;
|
||||
if (s == "warn") return SG_WARN;
|
||||
if (s == "alert") return SG_ALERT;
|
||||
|
||||
throw std::invalid_argument("Couldn't parse log prioirty:" + s);
|
||||
}
|
||||
|
||||
logstream&
|
||||
sglog()
|
||||
|
||||
@@ -37,27 +37,8 @@ class SGPath;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class LogCallback
|
||||
{
|
||||
public:
|
||||
virtual ~LogCallback() {}
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) = 0;
|
||||
|
||||
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||
protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
|
||||
static const char* debugClassToString(sgDebugClass c);
|
||||
static const char* debugPriorityToString(sgDebugPriority p);
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
class LogCallback;
|
||||
/**
|
||||
* Helper force a console on platforms where it might optional, when
|
||||
* we need to show a console. This basically means Windows at the
|
||||
@@ -105,6 +86,11 @@ public:
|
||||
|
||||
sgDebugPriority get_log_priority() const;
|
||||
|
||||
/**
|
||||
@brief convert a string value to a log prioirty.
|
||||
throws std::invalid_argument if the string is not valid
|
||||
*/
|
||||
static sgDebugPriority priorityFromString(const std::string& s);
|
||||
/**
|
||||
* set developer mode on/off. In developer mode, SG_DEV_WARN messags
|
||||
* are treated as warnings. In normal (non-developer) mode they are
|
||||
@@ -124,6 +110,14 @@ public:
|
||||
void log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg);
|
||||
|
||||
// overload of above, which can transfer ownership of the file-name.
|
||||
// this is unecesary overhead when logging from C++, since __FILE__ points
|
||||
// to constant data, but it's needed when the filename is Nasal data (for
|
||||
// example) since during shutdown the filename is freed by Nasal GC
|
||||
// asynchronously with the logging thread.
|
||||
void logCopyingFilename( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg);
|
||||
|
||||
/**
|
||||
* output formatted hex dump of memory block
|
||||
*/
|
||||
@@ -185,8 +179,6 @@ private:
|
||||
// constructor
|
||||
logstream();
|
||||
|
||||
std::vector<std::string> popup_msgs;
|
||||
|
||||
class LogStreamPrivate;
|
||||
|
||||
std::unique_ptr<LogStreamPrivate> d;
|
||||
|
||||
@@ -45,14 +45,6 @@ namespace simgear
|
||||
std::atomic<int> receiveDepth;
|
||||
std::atomic<int> sentMessageCount;
|
||||
|
||||
void UnlockList()
|
||||
{
|
||||
_lock.unlock();
|
||||
}
|
||||
void LockList()
|
||||
{
|
||||
_lock.lock();
|
||||
}
|
||||
public:
|
||||
Transmitter() : receiveDepth(0), sentMessageCount(0)
|
||||
{
|
||||
@@ -69,20 +61,17 @@ namespace simgear
|
||||
// most recently registered recipients should process the messages/events first.
|
||||
virtual void Register(IReceiver& r)
|
||||
{
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
recipient_list.push_back(&r);
|
||||
r.OnRegisteredAtTransmitter(this);
|
||||
if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end())
|
||||
deleted_recipients.remove(&r);
|
||||
|
||||
UnlockList();
|
||||
}
|
||||
|
||||
// Removes an object from receving message from this transmitter
|
||||
virtual void DeRegister(IReceiver& R)
|
||||
{
|
||||
LockList();
|
||||
//printf("Remove %x\n", &R);
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
if (recipient_list.size())
|
||||
{
|
||||
if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end())
|
||||
@@ -93,7 +82,6 @@ namespace simgear
|
||||
deleted_recipients.push_back(&R);
|
||||
}
|
||||
}
|
||||
UnlockList();
|
||||
}
|
||||
|
||||
// Notify all registered recipients. Stop when receipt status of abort or finished are received.
|
||||
@@ -107,69 +95,68 @@ namespace simgear
|
||||
ReceiptStatus return_status = ReceiptStatusNotProcessed;
|
||||
|
||||
sentMessageCount++;
|
||||
try
|
||||
|
||||
std::vector<IReceiver*> temp;
|
||||
{
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
if (receiveDepth == 0)
|
||||
deleted_recipients.clear();
|
||||
receiveDepth++;
|
||||
std::vector<IReceiver*> temp(recipient_list.size());
|
||||
|
||||
int idx = 0;
|
||||
for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++)
|
||||
{
|
||||
temp[idx++] = *i;
|
||||
temp.push_back(*i);
|
||||
}
|
||||
UnlockList();
|
||||
int tempSize = temp.size();
|
||||
for (int index = 0; index < tempSize; index++)
|
||||
}
|
||||
int tempSize = temp.size();
|
||||
for (int index = 0; index < tempSize; index++)
|
||||
{
|
||||
IReceiver* R = temp[index];
|
||||
{
|
||||
IReceiver* R = temp[index];
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
if (deleted_recipients.size())
|
||||
{
|
||||
if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end())
|
||||
{
|
||||
UnlockList();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
UnlockList();
|
||||
if (R)
|
||||
{
|
||||
ReceiptStatus rstat = R->Receive(M);
|
||||
switch (rstat)
|
||||
{
|
||||
case ReceiptStatusFail:
|
||||
return_status = ReceiptStatusFail;
|
||||
break;
|
||||
case ReceiptStatusPending:
|
||||
return_status = ReceiptStatusPending;
|
||||
break;
|
||||
case ReceiptStatusPendingFinished:
|
||||
return rstat;
|
||||
|
||||
case ReceiptStatusNotProcessed:
|
||||
break;
|
||||
case ReceiptStatusOK:
|
||||
if (return_status == ReceiptStatusNotProcessed)
|
||||
return_status = rstat;
|
||||
break;
|
||||
|
||||
case ReceiptStatusAbort:
|
||||
return ReceiptStatusAbort;
|
||||
|
||||
case ReceiptStatusFinished:
|
||||
return ReceiptStatusOK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (R)
|
||||
{
|
||||
ReceiptStatus rstat = R->Receive(M);
|
||||
switch (rstat)
|
||||
{
|
||||
case ReceiptStatusFail:
|
||||
return_status = ReceiptStatusFail;
|
||||
break;
|
||||
|
||||
case ReceiptStatusPending:
|
||||
return_status = ReceiptStatusPending;
|
||||
break;
|
||||
|
||||
case ReceiptStatusPendingFinished:
|
||||
return rstat;
|
||||
|
||||
case ReceiptStatusNotProcessed:
|
||||
break;
|
||||
|
||||
case ReceiptStatusOK:
|
||||
if (return_status == ReceiptStatusNotProcessed)
|
||||
return_status = rstat;
|
||||
break;
|
||||
|
||||
case ReceiptStatusAbort:
|
||||
return ReceiptStatusAbort;
|
||||
|
||||
case ReceiptStatusFinished:
|
||||
return ReceiptStatusOK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw;
|
||||
// return_status = ReceiptStatusAbort;
|
||||
}
|
||||
|
||||
receiveDepth--;
|
||||
return return_status;
|
||||
}
|
||||
@@ -177,9 +164,8 @@ namespace simgear
|
||||
// number of currently registered recipients
|
||||
virtual int Count()
|
||||
{
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
return recipient_list.size();
|
||||
UnlockList();
|
||||
}
|
||||
|
||||
// number of sent messages.
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include <sstream>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/math/sg_random.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "metar.hxx"
|
||||
@@ -631,30 +632,41 @@ bool SGMetar::scanWind()
|
||||
int dir;
|
||||
if (!strncmp(m, "VRB", 3))
|
||||
m += 3, dir = -1;
|
||||
else if (!strncmp(m, "///", 3)) // direction not measurable
|
||||
m += 3, dir = -1;
|
||||
else if (!scanNumber(&m, &dir, 3))
|
||||
return false;
|
||||
|
||||
int i;
|
||||
if (!scanNumber(&m, &i, 2, 3))
|
||||
if (!strncmp(m, "//", 2)) // speed not measurable
|
||||
m += 2, i = -1;
|
||||
else if (!scanNumber(&m, &i, 2, 3))
|
||||
return false;
|
||||
double speed = i;
|
||||
|
||||
double gust = NaN;
|
||||
if (*m == 'G') {
|
||||
m++;
|
||||
if (!scanNumber(&m, &i, 2, 3))
|
||||
if (!strncmp(m, "//", 2)) // speed not measurable
|
||||
m += 2, i = -1;
|
||||
else if (!scanNumber(&m, &i, 2, 3))
|
||||
return false;
|
||||
gust = i;
|
||||
|
||||
if (i != -1)
|
||||
gust = i;
|
||||
}
|
||||
|
||||
double factor;
|
||||
if (!strncmp(m, "KT", 2))
|
||||
m += 2, factor = SG_KT_TO_MPS;
|
||||
else if (!strncmp(m, "KMH", 3))
|
||||
else if (!strncmp(m, "KMH", 3)) // invalid Km/h
|
||||
m += 3, factor = SG_KMH_TO_MPS;
|
||||
else if (!strncmp(m, "KPH", 3)) // ??
|
||||
else if (!strncmp(m, "KPH", 3)) // invalid Km/h
|
||||
m += 3, factor = SG_KMH_TO_MPS;
|
||||
else if (!strncmp(m, "MPS", 3))
|
||||
m += 3, factor = 1.0;
|
||||
else if (!strncmp(m, " ", 1)) // default to Knots
|
||||
factor = SG_KT_TO_MPS;
|
||||
else
|
||||
return false;
|
||||
if (!scanBoundary(&m))
|
||||
@@ -674,18 +686,28 @@ bool SGMetar::scanVariability()
|
||||
{
|
||||
char *m = _m;
|
||||
int from, to;
|
||||
if (!scanNumber(&m, &from, 3))
|
||||
|
||||
if (!strncmp(m, "///", 3)) // direction not measurable
|
||||
m += 3, from = -1;
|
||||
else if (!scanNumber(&m, &from, 3))
|
||||
return false;
|
||||
|
||||
if (*m++ != 'V')
|
||||
return false;
|
||||
if (!scanNumber(&m, &to, 3))
|
||||
|
||||
if (!strncmp(m, "///", 3)) // direction not measurable
|
||||
m += 3, to = -1;
|
||||
else if (!scanNumber(&m, &to, 3))
|
||||
return false;
|
||||
|
||||
if (!scanBoundary(&m))
|
||||
return false;
|
||||
|
||||
_m = m;
|
||||
_wind_range_from = from;
|
||||
_wind_range_to = to;
|
||||
_grpcount++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -952,6 +974,8 @@ bool SGMetar::scanWeather()
|
||||
weather += string(a->text) + " ";
|
||||
if (!strcmp(a->id, "RA"))
|
||||
_rain = w.intensity;
|
||||
else if (!strcmp(a->id, "DZ"))
|
||||
_rain = LIGHT;
|
||||
else if (!strcmp(a->id, "HA"))
|
||||
_hail = w.intensity;
|
||||
else if (!strcmp(a->id, "SN"))
|
||||
@@ -1045,9 +1069,12 @@ bool SGMetar::scanSkyCondition()
|
||||
return false;
|
||||
m += i;
|
||||
|
||||
if (!strncmp(m, "///", 3)) // vis not measurable (e.g. because of heavy snowing)
|
||||
if (!strncmp(m, "///", 3)) { // vis not measurable (e.g. because of heavy snowing)
|
||||
m += 3, i = -1;
|
||||
else if (scanBoundary(&m)) {
|
||||
sg_srandom_time();
|
||||
// randomize the base height to avoid the black sky issue
|
||||
i = 50 + static_cast<int>(sg_random() * 250.0); // range [5,000, 30,000]
|
||||
} else if (scanBoundary(&m)) {
|
||||
_m = m;
|
||||
return true; // ignore single OVC/BKN/...
|
||||
} else if (!scanNumber(&m, &i, 3))
|
||||
|
||||
@@ -76,12 +76,38 @@ void test_sensor_failure_cloud()
|
||||
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
|
||||
}
|
||||
|
||||
void test_sensor_failure_wind()
|
||||
{
|
||||
SGMetar m1("2020/10/23 16:55 LIVD 231655Z /////KT 9999 OVC025 10/08 Q1020 RMK OVC VIS MIN 9999 BLU");
|
||||
SG_CHECK_EQUAL(m1.getWindDir(), -1);
|
||||
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), -1, TEST_EPSILON);
|
||||
|
||||
SGMetar m2("2020/10/21 16:55 LIVD 211655Z /////KT CAVOK 07/03 Q1023 RMK SKC VIS MIN 9999 BLU");
|
||||
SG_CHECK_EQUAL(m2.getWindDir(), -1);
|
||||
SG_CHECK_EQUAL_EP2(m2.getWindSpeed_kt(), -1, TEST_EPSILON);
|
||||
|
||||
SGMetar m3("2020/11/17 16:00 CYAZ 171600Z 14040G//KT 10SM -RA OVC012 12/11 A2895 RMK NS8 VIA CYXY SLP806 DENSITY ALT 900FT");
|
||||
SG_CHECK_EQUAL(m3.getWindDir(), 140);
|
||||
SG_CHECK_EQUAL_EP2(m3.getWindSpeed_kt(), 40, TEST_EPSILON);
|
||||
SG_CHECK_EQUAL_EP2(m3.getGustSpeed_kt(), SGMetarNaN, TEST_EPSILON);
|
||||
}
|
||||
|
||||
void test_wind_unit_not_specified()
|
||||
{
|
||||
SGMetar m1("2020/10/23 11:58 KLSV 231158Z 05010G14 10SM CLR 16/M04 A2992 RMK SLPNO WND DATA ESTMD ALSTG/SLP ESTMD 10320 20124 5//// $");
|
||||
SG_CHECK_EQUAL(m1.getWindDir(), 50);
|
||||
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 10.0, TEST_EPSILON);
|
||||
SG_CHECK_EQUAL_EP2(m1.getGustSpeed_kt(), 14.0, TEST_EPSILON);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try {
|
||||
test_basic();
|
||||
test_sensor_failure_weather();
|
||||
test_sensor_failure_cloud();
|
||||
test_sensor_failure_wind();
|
||||
test_wind_unit_not_specified();
|
||||
} catch (sg_exception& e) {
|
||||
cerr << "got exception:" << e.getMessage() << endl;
|
||||
return -1;
|
||||
|
||||
90
simgear/io/ArchiveExtractor_private.hxx
Normal file
90
simgear/io/ArchiveExtractor_private.hxx
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2021 James Turner - <james@flightgear.org>
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "untar.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class ArchiveExtractorPrivate
|
||||
{
|
||||
public:
|
||||
ArchiveExtractorPrivate(ArchiveExtractor* o) : outer(o)
|
||||
{
|
||||
assert(outer);
|
||||
}
|
||||
|
||||
virtual ~ArchiveExtractorPrivate() = default;
|
||||
|
||||
typedef enum {
|
||||
INVALID = 0,
|
||||
READING_HEADER,
|
||||
READING_FILE,
|
||||
READING_PADDING,
|
||||
READING_PAX_GLOBAL_ATTRIBUTES,
|
||||
READING_PAX_FILE_ATTRIBUTES,
|
||||
PRE_END_OF_ARCHVE,
|
||||
END_OF_ARCHIVE,
|
||||
ERROR_STATE, ///< states above this are error conditions
|
||||
BAD_ARCHIVE,
|
||||
BAD_DATA,
|
||||
FILTER_STOPPED
|
||||
} State;
|
||||
|
||||
State state = INVALID;
|
||||
ArchiveExtractor* outer = nullptr;
|
||||
|
||||
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
|
||||
|
||||
virtual void flush() = 0;
|
||||
|
||||
SGPath extractRootPath()
|
||||
{
|
||||
return outer->_rootPath;
|
||||
}
|
||||
|
||||
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
|
||||
{
|
||||
return outer->filterPath(pathToExtract);
|
||||
}
|
||||
|
||||
|
||||
bool isSafePath(const std::string& p) const
|
||||
{
|
||||
if (p.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject absolute paths
|
||||
if (p.at(0) == '/') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject paths containing '..'
|
||||
size_t doubleDot = p.find("..");
|
||||
if (doubleDot != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// on POSIX could use realpath to sanity check
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
@@ -35,10 +35,12 @@ set(SOURCES
|
||||
sg_socket.cxx
|
||||
sg_socket_udp.cxx
|
||||
HTTPClient.cxx
|
||||
HTTPTestApi_private.hxx
|
||||
HTTPFileRequest.cxx
|
||||
HTTPMemoryRequest.cxx
|
||||
HTTPRequest.cxx
|
||||
HTTPRepository.cxx
|
||||
HTTPRepository_private.hxx
|
||||
untar.cxx
|
||||
)
|
||||
|
||||
@@ -81,6 +83,7 @@ add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
|
||||
|
||||
add_executable(test_repository test_repository.cxx)
|
||||
target_link_libraries(test_repository ${TEST_LIBS})
|
||||
target_compile_definitions(test_repository PUBLIC BUILDING_TESTSUITE)
|
||||
add_test(http_repository ${EXECUTABLE_OUTPUT_PATH}/test_repository)
|
||||
|
||||
add_executable(test_untar test_untar.cxx)
|
||||
|
||||
@@ -61,6 +61,10 @@ public:
|
||||
|
||||
struct dns_ctx * ctx;
|
||||
static size_t instanceCounter;
|
||||
|
||||
using RequestVec = std::vector<Request_ptr>;
|
||||
|
||||
RequestVec _activeRequests;
|
||||
};
|
||||
|
||||
size_t Client::ClientPrivate::instanceCounter = 0;
|
||||
@@ -78,6 +82,11 @@ Request::~Request()
|
||||
{
|
||||
}
|
||||
|
||||
void Request::cancel()
|
||||
{
|
||||
_cancelled = true;
|
||||
}
|
||||
|
||||
bool Request::isTimeout() const
|
||||
{
|
||||
return (time(NULL) - _start) > _timeout_secs;
|
||||
@@ -114,18 +123,20 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
|
||||
{
|
||||
SRVRequest * r = static_cast<SRVRequest*>(data);
|
||||
if (result) {
|
||||
r->cname = result->dnssrv_cname;
|
||||
r->qname = result->dnssrv_qname;
|
||||
r->ttl = result->dnssrv_ttl;
|
||||
for (int i = 0; i < result->dnssrv_nrr; i++) {
|
||||
SRVRequest::SRV_ptr srv(new SRVRequest::SRV);
|
||||
r->entries.push_back(srv);
|
||||
srv->priority = result->dnssrv_srv[i].priority;
|
||||
srv->weight = result->dnssrv_srv[i].weight;
|
||||
srv->port = result->dnssrv_srv[i].port;
|
||||
srv->target = result->dnssrv_srv[i].name;
|
||||
if (!r->isCancelled()) {
|
||||
r->cname = result->dnssrv_cname;
|
||||
r->qname = result->dnssrv_qname;
|
||||
r->ttl = result->dnssrv_ttl;
|
||||
for (int i = 0; i < result->dnssrv_nrr; i++) {
|
||||
SRVRequest::SRV_ptr srv(new SRVRequest::SRV);
|
||||
r->entries.push_back(srv);
|
||||
srv->priority = result->dnssrv_srv[i].priority;
|
||||
srv->weight = result->dnssrv_srv[i].weight;
|
||||
srv->port = result->dnssrv_srv[i].port;
|
||||
srv->target = result->dnssrv_srv[i].name;
|
||||
}
|
||||
std::sort(r->entries.begin(), r->entries.end(), sortSRV);
|
||||
}
|
||||
std::sort( r->entries.begin(), r->entries.end(), sortSRV );
|
||||
free(result);
|
||||
}
|
||||
r->setComplete();
|
||||
@@ -134,11 +145,16 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
|
||||
void SRVRequest::submit( Client * client )
|
||||
{
|
||||
// if service is defined, pass service and protocol
|
||||
if (!dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(), _service.empty() ? NULL : _protocol.c_str(), 0, dnscbSRV, this )) {
|
||||
auto q = dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(),
|
||||
_service.empty() ? NULL : _protocol.c_str(),
|
||||
0, dnscbSRV, this);
|
||||
|
||||
if (!q) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
|
||||
return;
|
||||
}
|
||||
_start = time(NULL);
|
||||
_query = q;
|
||||
}
|
||||
|
||||
TXTRequest::TXTRequest( const std::string & dn ) :
|
||||
@@ -151,17 +167,24 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
|
||||
{
|
||||
TXTRequest * r = static_cast<TXTRequest*>(data);
|
||||
if (result) {
|
||||
r->cname = result->dnstxt_cname;
|
||||
r->qname = result->dnstxt_qname;
|
||||
r->ttl = result->dnstxt_ttl;
|
||||
for (int i = 0; i < result->dnstxt_nrr; i++) {
|
||||
//TODO: interprete the .len field of dnstxt_txt?
|
||||
string txt = string((char*)result->dnstxt_txt[i].txt);
|
||||
r->entries.push_back( txt );
|
||||
string_list tokens = simgear::strutils::split( txt, "=", 1 );
|
||||
if( tokens.size() == 2 ) {
|
||||
r->attributes[tokens[0]] = tokens[1];
|
||||
}
|
||||
if (!r->isCancelled()) {
|
||||
r->cname = result->dnstxt_cname;
|
||||
r->qname = result->dnstxt_qname;
|
||||
r->ttl = result->dnstxt_ttl;
|
||||
for (int i = 0; i < result->dnstxt_nrr; i++) {
|
||||
//TODO: interprete the .len field of dnstxt_txt?
|
||||
auto rawTxt = reinterpret_cast<char*>(result->dnstxt_txt[i].txt);
|
||||
if (!rawTxt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const string txt{rawTxt};
|
||||
r->entries.push_back(txt);
|
||||
string_list tokens = simgear::strutils::split(txt, "=", 1);
|
||||
if (tokens.size() == 2) {
|
||||
r->attributes[tokens[0]] = tokens[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
free(result);
|
||||
}
|
||||
@@ -171,11 +194,13 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
|
||||
void TXTRequest::submit( Client * client )
|
||||
{
|
||||
// protocol and service an already encoded in DN so pass in NULL for both
|
||||
if (!dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this )) {
|
||||
auto q = dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this);
|
||||
if (!q) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
|
||||
return;
|
||||
}
|
||||
_start = time(NULL);
|
||||
_query = q;
|
||||
}
|
||||
|
||||
|
||||
@@ -190,27 +215,29 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
|
||||
{
|
||||
NAPTRRequest * r = static_cast<NAPTRRequest*>(data);
|
||||
if (result) {
|
||||
r->cname = result->dnsnaptr_cname;
|
||||
r->qname = result->dnsnaptr_qname;
|
||||
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 )
|
||||
continue;
|
||||
if (!r->isCancelled()) {
|
||||
r->cname = result->dnsnaptr_cname;
|
||||
r->qname = result->dnsnaptr_qname;
|
||||
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)
|
||||
continue;
|
||||
|
||||
//TODO: case ignore and result flags may have more than one flag
|
||||
if( !r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags )
|
||||
continue;
|
||||
//TODO: case ignore and result flags may have more than one flag
|
||||
if (!r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags)
|
||||
continue;
|
||||
|
||||
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
|
||||
r->entries.push_back(naptr);
|
||||
naptr->order = result->dnsnaptr_naptr[i].order;
|
||||
naptr->preference = result->dnsnaptr_naptr[i].preference;
|
||||
naptr->flags = result->dnsnaptr_naptr[i].flags;
|
||||
naptr->service = result->dnsnaptr_naptr[i].service;
|
||||
naptr->regexp = result->dnsnaptr_naptr[i].regexp;
|
||||
naptr->replacement = result->dnsnaptr_naptr[i].replacement;
|
||||
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
|
||||
r->entries.push_back(naptr);
|
||||
naptr->order = result->dnsnaptr_naptr[i].order;
|
||||
naptr->preference = result->dnsnaptr_naptr[i].preference;
|
||||
naptr->flags = result->dnsnaptr_naptr[i].flags;
|
||||
naptr->service = result->dnsnaptr_naptr[i].service;
|
||||
naptr->regexp = result->dnsnaptr_naptr[i].regexp;
|
||||
naptr->replacement = result->dnsnaptr_naptr[i].replacement;
|
||||
}
|
||||
std::sort(r->entries.begin(), r->entries.end(), sortNAPTR);
|
||||
}
|
||||
std::sort( r->entries.begin(), r->entries.end(), sortNAPTR );
|
||||
free(result);
|
||||
}
|
||||
r->setComplete();
|
||||
@@ -218,11 +245,13 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
|
||||
|
||||
void NAPTRRequest::submit( Client * client )
|
||||
{
|
||||
if (!dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this )) {
|
||||
auto q = dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this);
|
||||
if (!q) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
|
||||
return;
|
||||
}
|
||||
_start = time(NULL);
|
||||
_query = q;
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +266,7 @@ Client::Client() :
|
||||
|
||||
void Client::makeRequest(const Request_ptr& r)
|
||||
{
|
||||
d->_activeRequests.push_back(r);
|
||||
r->submit(this);
|
||||
}
|
||||
|
||||
@@ -247,6 +277,19 @@ void Client::update(int waitTimeout)
|
||||
return;
|
||||
|
||||
dns_ioevent(d->ctx, now);
|
||||
|
||||
// drop our owning ref to completed requests,
|
||||
// and cancel any which timed out
|
||||
auto it = std::remove_if(d->_activeRequests.begin(), d->_activeRequests.end(),
|
||||
[this](const Request_ptr& r) {
|
||||
if (r->isTimeout()) {
|
||||
dns_cancel(d->ctx, reinterpret_cast<struct dns_query*>(r->_query));
|
||||
return true;
|
||||
}
|
||||
|
||||
return r->isComplete();
|
||||
});
|
||||
d->_activeRequests.erase(it, d->_activeRequests.end());
|
||||
}
|
||||
|
||||
} // of namespace DNS
|
||||
|
||||
@@ -40,28 +40,38 @@ namespace DNS
|
||||
{
|
||||
|
||||
class Client;
|
||||
|
||||
using UDNSQueryPtr = void*;
|
||||
|
||||
class Request : public SGReferenced
|
||||
{
|
||||
public:
|
||||
Request( const std::string & dn );
|
||||
virtual ~Request();
|
||||
std::string getDn() const { return _dn; }
|
||||
const std::string& getDn() const { return _dn; }
|
||||
int getType() const { return _type; }
|
||||
bool isComplete() const { return _complete; }
|
||||
bool isTimeout() const;
|
||||
void setComplete( bool b = true ) { _complete = b; }
|
||||
bool isCancelled() const { return _cancelled; }
|
||||
|
||||
virtual void submit( Client * client) = 0;
|
||||
|
||||
void cancel();
|
||||
|
||||
std::string cname;
|
||||
std::string qname;
|
||||
unsigned ttl;
|
||||
protected:
|
||||
friend class Client;
|
||||
|
||||
UDNSQueryPtr _query = nullptr;
|
||||
std::string _dn;
|
||||
int _type;
|
||||
bool _complete;
|
||||
time_t _timeout_secs;
|
||||
time_t _start;
|
||||
bool _cancelled = false;
|
||||
};
|
||||
typedef SGSharedPtr<Request> Request_ptr;
|
||||
|
||||
@@ -69,7 +79,7 @@ class NAPTRRequest : public Request
|
||||
{
|
||||
public:
|
||||
NAPTRRequest( const std::string & dn );
|
||||
virtual void submit( Client * client );
|
||||
void submit(Client* client) override;
|
||||
|
||||
struct NAPTR : SGReferenced {
|
||||
int order;
|
||||
@@ -92,7 +102,7 @@ class SRVRequest : public Request
|
||||
public:
|
||||
SRVRequest( const std::string & dn );
|
||||
SRVRequest( const std::string & dn, const string & service, const string & protocol );
|
||||
virtual void submit( Client * client );
|
||||
void submit(Client* client) override;
|
||||
|
||||
struct SRV : SGReferenced {
|
||||
int priority;
|
||||
@@ -112,7 +122,7 @@ class TXTRequest : public Request
|
||||
{
|
||||
public:
|
||||
TXTRequest( const std::string & dn );
|
||||
virtual void submit( Client * client );
|
||||
void submit(Client* client) override;
|
||||
|
||||
typedef std::vector<string> TXT_list;
|
||||
typedef std::map<std::string,std::string> TXT_Attribute_map;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
|
||||
#include <simgear/simgear_config.h>
|
||||
|
||||
#include <curl/multi.h>
|
||||
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
|
||||
@@ -47,6 +46,9 @@
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "HTTPClient_private.hxx"
|
||||
#include "HTTPTestApi_private.hxx"
|
||||
|
||||
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
|
||||
#include "version.h"
|
||||
#else
|
||||
@@ -64,62 +66,23 @@ namespace HTTP
|
||||
extern const int DEFAULT_HTTP_PORT = 80;
|
||||
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
class Connection;
|
||||
typedef std::multimap<std::string, Connection*> ConnectionDict;
|
||||
typedef std::list<Request_ptr> RequestList;
|
||||
|
||||
class Client::ClientPrivate
|
||||
{
|
||||
public:
|
||||
CURLM* curlMulti;
|
||||
|
||||
void createCurlMulti()
|
||||
{
|
||||
curlMulti = curl_multi_init();
|
||||
// see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
|
||||
// we request HTTP 1.1 pipelining
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
|
||||
void Client::ClientPrivate::createCurlMulti() {
|
||||
curlMulti = curl_multi_init();
|
||||
// see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
|
||||
// we request HTTP 1.1 pipelining
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
|
||||
#if (LIBCURL_VERSION_MINOR >= 30)
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) maxConnections);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH,
|
||||
(long) maxPipelineDepth);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS,
|
||||
(long) maxHostConnections);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS,
|
||||
(long)maxConnections);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH,
|
||||
(long)maxPipelineDepth);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS,
|
||||
(long)maxHostConnections);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::map<Request_ptr, CURL*> RequestCurlMap;
|
||||
RequestCurlMap requests;
|
||||
|
||||
std::string userAgent;
|
||||
std::string proxy;
|
||||
int proxyPort;
|
||||
std::string proxyAuth;
|
||||
unsigned int maxConnections;
|
||||
unsigned int maxHostConnections;
|
||||
unsigned int maxPipelineDepth;
|
||||
|
||||
RequestList pendingRequests;
|
||||
|
||||
SGTimeStamp timeTransferSample;
|
||||
unsigned int bytesTransferred;
|
||||
unsigned int lastTransferRate;
|
||||
uint64_t totalBytesDownloaded;
|
||||
};
|
||||
|
||||
Client::Client() :
|
||||
d(new ClientPrivate)
|
||||
Client::Client()
|
||||
{
|
||||
d->proxyPort = 0;
|
||||
d->maxConnections = 4;
|
||||
d->maxHostConnections = 4;
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
d->timeTransferSample.stamp();
|
||||
d->totalBytesDownloaded = 0;
|
||||
d->maxPipelineDepth = 5;
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
|
||||
static bool didInitCurlGlobal = false;
|
||||
static std::mutex initMutex;
|
||||
|
||||
@@ -129,7 +92,7 @@ Client::Client() :
|
||||
didInitCurlGlobal = true;
|
||||
}
|
||||
|
||||
d->createCurlMulti();
|
||||
reset();
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
@@ -161,6 +124,27 @@ void Client::setMaxPipelineDepth(unsigned int depth)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Client::reset()
|
||||
{
|
||||
if (d.get()) {
|
||||
curl_multi_cleanup(d->curlMulti);
|
||||
}
|
||||
|
||||
d.reset(new ClientPrivate);
|
||||
|
||||
d->proxyPort = 0;
|
||||
d->maxConnections = 4;
|
||||
d->maxHostConnections = 4;
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
d->timeTransferSample.stamp();
|
||||
d->totalBytesDownloaded = 0;
|
||||
d->maxPipelineDepth = 5;
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
d->tlsCertificatePath = SGPath::fromEnv("SIMGEAR_TLS_CERT_PATH");
|
||||
d->createCurlMulti();
|
||||
}
|
||||
|
||||
void Client::update(int waitTimeout)
|
||||
{
|
||||
if (d->requests.empty()) {
|
||||
@@ -171,32 +155,22 @@ void Client::update(int waitTimeout)
|
||||
}
|
||||
|
||||
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;
|
||||
int numFds;
|
||||
CURLMcode mc = curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
|
||||
if (mc != CURLM_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "curl_multi_wait failed:" << curl_multi_strerror(mc));
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
mc = curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
if (mc == CURLM_CALL_MULTI_PERFORM) {
|
||||
// we could loop here, but don't want to get blocked
|
||||
// also this shouldn't ocurr in any modern libCurl
|
||||
curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
} else if (mc != CURLM_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "curl_multi_perform failed:" << curl_multi_strerror(mc));
|
||||
return;
|
||||
}
|
||||
|
||||
CURLMsg* msg;
|
||||
while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) {
|
||||
@@ -221,12 +195,23 @@ void Client::update(int waitTimeout)
|
||||
assert(it->second == e);
|
||||
d->requests.erase(it);
|
||||
|
||||
if (msg->data.result == 0) {
|
||||
req->responseComplete();
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "CURL Result:" << msg->data.result << " " << curl_easy_strerror(msg->data.result));
|
||||
req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result));
|
||||
}
|
||||
bool doProcess = true;
|
||||
if (d->testsuiteResponseDoneCallback) {
|
||||
doProcess =
|
||||
!d->testsuiteResponseDoneCallback(msg->data.result, req);
|
||||
}
|
||||
|
||||
if (doProcess) {
|
||||
if (msg->data.result == 0) {
|
||||
req->responseComplete();
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN,
|
||||
"CURL Result:" << msg->data.result << " "
|
||||
<< curl_easy_strerror(msg->data.result));
|
||||
req->setFailure(msg->data.result,
|
||||
curl_easy_strerror(msg->data.result));
|
||||
}
|
||||
}
|
||||
|
||||
curl_multi_remove_handle(d->curlMulti, e);
|
||||
curl_easy_cleanup(e);
|
||||
@@ -285,6 +270,11 @@ void Client::makeRequest(const Request_ptr& r)
|
||||
|
||||
curl_easy_setopt(curlRequest, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
if (!d->tlsCertificatePath.isNull()) {
|
||||
const auto utf8 = d->tlsCertificatePath.utf8Str();
|
||||
curl_easy_setopt(curlRequest, CURLOPT_CAINFO, utf8.c_str());
|
||||
}
|
||||
|
||||
if (!d->proxy.empty()) {
|
||||
curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str());
|
||||
curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort);
|
||||
@@ -552,6 +542,17 @@ void Client::clearAllConnections()
|
||||
d->createCurlMulti();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
void TestApi::setResponseDoneCallback(Client *cl, ResponseDoneCallback cb) {
|
||||
cl->d->testsuiteResponseDoneCallback = cb;
|
||||
}
|
||||
|
||||
void TestApi::markRequestAsFailed(Request_ptr req, int curlCode,
|
||||
const std::string &message) {
|
||||
req->setFailure(curlCode, message);
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#ifndef SG_HTTP_CLIENT_HXX
|
||||
#define SG_HTTP_CLIENT_HXX
|
||||
|
||||
#include <memory> // for std::unique_ptr
|
||||
#include <functional>
|
||||
#include <memory> // for std::unique_ptr
|
||||
#include <stdint.h> // for uint_64t
|
||||
|
||||
#include <simgear/io/HTTPFileRequest.hxx>
|
||||
@@ -47,6 +48,8 @@ public:
|
||||
|
||||
void update(int waitTimeout = 0);
|
||||
|
||||
void reset();
|
||||
|
||||
void makeRequest(const Request_ptr& r);
|
||||
|
||||
void cancelRequest(const Request_ptr& r, std::string reason = std::string());
|
||||
@@ -123,6 +126,7 @@ private:
|
||||
|
||||
friend class Connection;
|
||||
friend class Request;
|
||||
friend class TestApi;
|
||||
|
||||
class ClientPrivate;
|
||||
std::unique_ptr<ClientPrivate> d;
|
||||
|
||||
68
simgear/io/HTTPClient_private.hxx
Normal file
68
simgear/io/HTTPClient_private.hxx
Normal file
@@ -0,0 +1,68 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
|
||||
#include <curl/multi.h>
|
||||
|
||||
namespace simgear {
|
||||
namespace HTTP {
|
||||
|
||||
typedef std::list<Request_ptr> RequestList;
|
||||
|
||||
using ResponseDoneCallback =
|
||||
std::function<bool(int curlResult, Request_ptr req)>;
|
||||
|
||||
class Client::ClientPrivate {
|
||||
public:
|
||||
CURLM *curlMulti;
|
||||
|
||||
void createCurlMulti();
|
||||
|
||||
typedef std::map<Request_ptr, CURL *> RequestCurlMap;
|
||||
RequestCurlMap requests;
|
||||
|
||||
std::string userAgent;
|
||||
std::string proxy;
|
||||
int proxyPort;
|
||||
std::string proxyAuth;
|
||||
unsigned int maxConnections;
|
||||
unsigned int maxHostConnections;
|
||||
unsigned int maxPipelineDepth;
|
||||
|
||||
RequestList pendingRequests;
|
||||
|
||||
SGTimeStamp timeTransferSample;
|
||||
unsigned int bytesTransferred;
|
||||
unsigned int lastTransferRate;
|
||||
uint64_t totalBytesDownloaded;
|
||||
|
||||
SGPath tlsCertificatePath;
|
||||
|
||||
// only used by unit-tests / test-api, but
|
||||
// only costs us a pointe here to declare it.
|
||||
ResponseDoneCallback testsuiteResponseDoneCallback;
|
||||
};
|
||||
|
||||
} // namespace HTTP
|
||||
|
||||
} // namespace simgear
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
||||
#ifndef SG_IO_HTTP_REPOSITORY_HXX
|
||||
#define SG_IO_HTTP_REPOSITORY_HXX
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
@@ -32,49 +33,85 @@ class HTTPRepoPrivate;
|
||||
class HTTPRepository
|
||||
{
|
||||
public:
|
||||
enum ResultCode {
|
||||
REPO_NO_ERROR = 0,
|
||||
REPO_ERROR_NOT_FOUND,
|
||||
REPO_ERROR_SOCKET,
|
||||
SVN_ERROR_XML,
|
||||
SVN_ERROR_TXDELTA,
|
||||
REPO_ERROR_IO,
|
||||
REPO_ERROR_CHECKSUM,
|
||||
REPO_ERROR_FILE_NOT_FOUND,
|
||||
REPO_ERROR_HTTP,
|
||||
REPO_ERROR_CANCELLED,
|
||||
REPO_PARTIAL_UPDATE
|
||||
enum ResultCode {
|
||||
REPO_NO_ERROR = 0,
|
||||
REPO_ERROR_NOT_FOUND,
|
||||
REPO_ERROR_SOCKET,
|
||||
SVN_ERROR_XML,
|
||||
SVN_ERROR_TXDELTA,
|
||||
REPO_ERROR_IO,
|
||||
REPO_ERROR_CHECKSUM,
|
||||
REPO_ERROR_FILE_NOT_FOUND,
|
||||
REPO_ERROR_HTTP,
|
||||
REPO_ERROR_CANCELLED,
|
||||
REPO_PARTIAL_UPDATE ///< repository is working, but file-level failures
|
||||
///< occurred
|
||||
};
|
||||
|
||||
HTTPRepository(const SGPath &root, HTTP::Client *cl);
|
||||
virtual ~HTTPRepository();
|
||||
|
||||
virtual SGPath fsBase() const;
|
||||
|
||||
virtual void setBaseUrl(const std::string &url);
|
||||
virtual std::string baseUrl() const;
|
||||
|
||||
virtual HTTP::Client *http() const;
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isDoingSync() const;
|
||||
|
||||
/**
|
||||
@brief call this periodically to progress non-network tasks
|
||||
*/
|
||||
void process();
|
||||
|
||||
virtual ResultCode failure() const;
|
||||
|
||||
virtual size_t bytesToDownload() const;
|
||||
|
||||
virtual size_t bytesDownloaded() const;
|
||||
|
||||
virtual size_t bytesToExtract() const;
|
||||
|
||||
/**
|
||||
* optionally provide the location of an installer copy of this
|
||||
* repository. When a file is missing it will be copied from this tree.
|
||||
*/
|
||||
void setInstalledCopyPath(const SGPath ©Path);
|
||||
|
||||
static std::string resultCodeAsString(ResultCode code);
|
||||
|
||||
enum class SyncAction { Add, Update, Delete, UpToDate };
|
||||
|
||||
enum EntryType { FileType, DirectoryType, TarballType };
|
||||
|
||||
struct SyncItem {
|
||||
const std::string directory; // relative path in the repository
|
||||
const EntryType type;
|
||||
const std::string filename;
|
||||
const SyncAction action;
|
||||
const SGPath pathOnDisk; // path the entry does / will have
|
||||
};
|
||||
|
||||
using SyncPredicate = std::function<bool(const SyncItem &item)>;
|
||||
|
||||
void setFilter(SyncPredicate sp);
|
||||
|
||||
struct Failure {
|
||||
SGPath path;
|
||||
ResultCode error;
|
||||
};
|
||||
|
||||
HTTPRepository(const SGPath& root, HTTP::Client* cl);
|
||||
virtual ~HTTPRepository();
|
||||
|
||||
virtual SGPath fsBase() const;
|
||||
|
||||
virtual void setBaseUrl(const std::string& url);
|
||||
virtual std::string baseUrl() const;
|
||||
|
||||
virtual HTTP::Client* http() const;
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isDoingSync() const;
|
||||
|
||||
virtual ResultCode failure() const;
|
||||
|
||||
virtual size_t bytesToDownload() const;
|
||||
|
||||
virtual size_t bytesDownloaded() const;
|
||||
using FailureVec = std::vector<Failure>;
|
||||
|
||||
/**
|
||||
* optionally provide the location of an installer copy of this
|
||||
* repository. When a file is missing it will be copied from this tree.
|
||||
* @brief return file-level failures
|
||||
*/
|
||||
void setInstalledCopyPath(const SGPath& copyPath);
|
||||
|
||||
static std::string resultCodeAsString(ResultCode code);
|
||||
FailureVec failures() const;
|
||||
|
||||
private:
|
||||
private:
|
||||
bool isBare() const;
|
||||
|
||||
std::unique_ptr<HTTPRepoPrivate> _d;
|
||||
|
||||
127
simgear/io/HTTPRepository_private.hxx
Normal file
127
simgear/io/HTTPRepository_private.hxx
Normal file
@@ -0,0 +1,127 @@
|
||||
// HTTPRepository.cxx -- plain HTTP TerraSync remote client
|
||||
//
|
||||
// Copyright (C) 20126 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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 program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
// USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
#include "HTTPRepository.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class HTTPDirectory;
|
||||
using HTTPDirectory_ptr = std::unique_ptr<HTTPDirectory>;
|
||||
|
||||
class HTTPRepoGetRequest : public HTTP::Request {
|
||||
public:
|
||||
HTTPRepoGetRequest(HTTPDirectory *d, const std::string &u)
|
||||
: HTTP::Request(u), _directory(d) {}
|
||||
|
||||
virtual void cancel();
|
||||
|
||||
size_t contentSize() const { return _contentSize; }
|
||||
|
||||
void setContentSize(size_t sz) { _contentSize = sz; }
|
||||
|
||||
protected:
|
||||
HTTPDirectory *_directory;
|
||||
size_t _contentSize = 0;
|
||||
};
|
||||
|
||||
using RepoRequestPtr = SGSharedPtr<HTTPRepoGetRequest>;
|
||||
|
||||
class HTTPRepoPrivate {
|
||||
public:
|
||||
|
||||
|
||||
HTTPRepository::FailureVec failures;
|
||||
int maxPermittedFailures = 16;
|
||||
|
||||
HTTPRepoPrivate(HTTPRepository* parent)
|
||||
: p(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~HTTPRepoPrivate();
|
||||
|
||||
HTTPRepository *p; // link back to outer
|
||||
HTTP::Client* http = nullptr;
|
||||
std::string baseUrl;
|
||||
SGPath basePath;
|
||||
bool isUpdating = false;
|
||||
HTTPRepository::ResultCode status = HTTPRepository::REPO_NO_ERROR;
|
||||
HTTPDirectory_ptr rootDir;
|
||||
size_t totalDownloaded = 0;
|
||||
size_t bytesToExtract = 0;
|
||||
size_t bytesExtracted = 0;
|
||||
HTTPRepository::SyncPredicate syncPredicate;
|
||||
|
||||
HTTP::Request_ptr updateFile(HTTPDirectory *dir, const std::string &name,
|
||||
size_t sz);
|
||||
HTTP::Request_ptr updateDir(HTTPDirectory *dir, const std::string &hash,
|
||||
size_t sz);
|
||||
|
||||
void failedToGetRootIndex(HTTPRepository::ResultCode st);
|
||||
void failedToUpdateChild(const SGPath &relativePath,
|
||||
HTTPRepository::ResultCode fileStatus);
|
||||
|
||||
void updatedChildSuccessfully(const SGPath &relativePath);
|
||||
|
||||
void checkForComplete();
|
||||
|
||||
typedef std::vector<RepoRequestPtr> RequestVector;
|
||||
RequestVector queuedRequests, activeRequests;
|
||||
|
||||
void makeRequest(RepoRequestPtr req);
|
||||
|
||||
enum class RequestFinish { Done, Retry };
|
||||
|
||||
void finishedRequest(const RepoRequestPtr &req, RequestFinish retryRequest);
|
||||
|
||||
HTTPDirectory *getOrCreateDirectory(const std::string &path);
|
||||
bool deleteDirectory(const std::string &relPath, const SGPath &absPath);
|
||||
|
||||
|
||||
typedef std::vector<HTTPDirectory_ptr> DirectoryVector;
|
||||
DirectoryVector directories;
|
||||
|
||||
void scheduleUpdateOfChildren(HTTPDirectory *dir);
|
||||
|
||||
SGPath installedCopyPath;
|
||||
|
||||
int countDirtyHashCaches() const;
|
||||
void flushHashCaches();
|
||||
|
||||
enum ProcessResult { ProcessContinue, ProcessDone, ProcessFailed };
|
||||
|
||||
using RepoProcessTask = std::function<ProcessResult(HTTPRepoPrivate *repo)>;
|
||||
|
||||
void addTask(RepoProcessTask task);
|
||||
|
||||
std::deque<RepoProcessTask> pendingTasks;
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
@@ -56,6 +56,15 @@ Request::~Request()
|
||||
|
||||
}
|
||||
|
||||
void Request::prepareForRetry() {
|
||||
setReadyState(UNSENT);
|
||||
_willClose = false;
|
||||
_connectionCloseHeader = false;
|
||||
_responseStatus = 0;
|
||||
_responseLength = 0;
|
||||
_receivedBodyBytes = 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::done(const Callback& cb)
|
||||
{
|
||||
@@ -193,13 +202,16 @@ void Request::onDone()
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onFail()
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_INFO,
|
||||
"request failed:" << url() << " : "
|
||||
<< responseCode() << "/" << responseReason()
|
||||
);
|
||||
// log if we FAIELD< but not if we CANCELLED
|
||||
if (_ready_state == FAILED) {
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_INFO,
|
||||
"request failed:" << url() << " : "
|
||||
<< responseCode() << "/" << responseReason()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -344,12 +356,17 @@ void Request::setSuccess(int code)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setFailure(int code, const std::string& reason)
|
||||
{
|
||||
// we use -1 for cancellation, don't be noisy in that case
|
||||
if (code >= 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason);
|
||||
}
|
||||
|
||||
_responseStatus = code;
|
||||
_responseReason = reason;
|
||||
|
||||
if( !isComplete() )
|
||||
setReadyState(FAILED);
|
||||
if( !isComplete() ) {
|
||||
setReadyState(code < 0 ? CANCELLED : FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -373,6 +390,12 @@ void Request::setReadyState(ReadyState state)
|
||||
|
||||
_cb_fail(this);
|
||||
}
|
||||
else if (state == CANCELLED )
|
||||
{
|
||||
onFail(); // do this for compatability
|
||||
onAlways();
|
||||
_cb_fail(this);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
@@ -402,7 +425,7 @@ bool Request::serverSupportsPipelining() const
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::isComplete() const
|
||||
{
|
||||
return _ready_state == DONE || _ready_state == FAILED;
|
||||
return _ready_state == DONE || _ready_state == FAILED || _ready_state == CANCELLED;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -54,7 +54,8 @@ public:
|
||||
HEADERS_RECEIVED,
|
||||
LOADING,
|
||||
DONE,
|
||||
FAILED
|
||||
FAILED,
|
||||
CANCELLED
|
||||
};
|
||||
|
||||
virtual ~Request();
|
||||
@@ -207,7 +208,9 @@ public:
|
||||
*/
|
||||
bool serverSupportsPipelining() const;
|
||||
|
||||
protected:
|
||||
virtual void prepareForRetry();
|
||||
|
||||
protected:
|
||||
Request(const std::string& url, const std::string method = "GET");
|
||||
|
||||
virtual void requestStart();
|
||||
@@ -221,12 +224,14 @@ protected:
|
||||
virtual void onFail();
|
||||
virtual void onAlways();
|
||||
|
||||
void setFailure(int code, const std::string& reason);
|
||||
void setSuccess(int code);
|
||||
private:
|
||||
void setFailure(int code, const std::string &reason);
|
||||
|
||||
private:
|
||||
friend class Client;
|
||||
friend class Connection;
|
||||
friend class ContentDecoder;
|
||||
friend class TestApi;
|
||||
|
||||
Request(const Request&); // = delete;
|
||||
Request& operator=(const Request&); // = delete;
|
||||
|
||||
45
simgear/io/HTTPTestApi_private.hxx
Normal file
45
simgear/io/HTTPTestApi_private.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
namespace simgear {
|
||||
namespace HTTP {
|
||||
|
||||
class Client;
|
||||
|
||||
using ResponseDoneCallback =
|
||||
std::function<bool(int curlResult, Request_ptr req)>;
|
||||
|
||||
/**
|
||||
* @brief this API is for unit-testing HTTP code.
|
||||
* Don't use it for anything else. It's for unit-testing.
|
||||
*/
|
||||
class TestApi {
|
||||
public:
|
||||
// alow test suite to manipulate requests to simulate network errors;
|
||||
// without this, it's hard to provoke certain failures in a loop-back
|
||||
// network sitation.
|
||||
static void setResponseDoneCallback(Client *cl, ResponseDoneCallback cb);
|
||||
|
||||
static void markRequestAsFailed(Request_ptr req, int curlCode,
|
||||
const std::string &message);
|
||||
};
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
@@ -185,6 +185,9 @@ gzfilebuf::setcompressionstrategy( int comp_strategy )
|
||||
|
||||
z_off_t
|
||||
gzfilebuf::approxOffset() {
|
||||
#ifdef __OpenBSD__
|
||||
z_off_t res = 0;
|
||||
#else
|
||||
z_off_t res = gzoffset(file);
|
||||
|
||||
if (res == -1) {
|
||||
@@ -201,7 +204,7 @@ gzfilebuf::approxOffset() {
|
||||
SG_LOG( SG_GENERAL, SG_ALERT, errMsg );
|
||||
throw sg_io_exception(errMsg);
|
||||
}
|
||||
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,28 +23,43 @@
|
||||
// $Id$
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string.h> // for memcpy()
|
||||
#include <errno.h>
|
||||
|
||||
#include "lowlevel.hxx"
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
#include "lowlevel.hxx"
|
||||
|
||||
static int read_error = false ;
|
||||
static int write_error = false ;
|
||||
thread_local SGPath thread_gzPath;
|
||||
|
||||
void sgClearReadError() { read_error = false; }
|
||||
void sgClearWriteError() { write_error = false; }
|
||||
int sgReadError() { return read_error ; }
|
||||
int sgWriteError() { return write_error ; }
|
||||
void setThreadLocalSimgearReadPath(const SGPath& path)
|
||||
{
|
||||
thread_gzPath = path;
|
||||
}
|
||||
|
||||
static std::string gzErrorMessage(gzFile fd)
|
||||
{
|
||||
int errNum = 0;
|
||||
const char *gzMsg = gzerror(fd, &errNum);
|
||||
|
||||
if (errNum == Z_ERRNO) {
|
||||
return simgear::strutils::error_string(errno);
|
||||
} else if (gzMsg) {
|
||||
return {gzMsg};
|
||||
}
|
||||
|
||||
return {"GZError: no string code for code:" + std::to_string(errNum)};
|
||||
}
|
||||
|
||||
void sgReadChar ( gzFile fd, char *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(char) ) != sizeof(char) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadChar: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +67,7 @@ void sgReadChar ( gzFile fd, char *var )
|
||||
void sgWriteChar ( gzFile fd, const char var )
|
||||
{
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(char) ) != sizeof(char) ) {
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteChar: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +76,8 @@ void sgReadFloat ( gzFile fd, float *var )
|
||||
{
|
||||
union { float v; uint32_t u; } buf;
|
||||
if ( gzread ( fd, &buf.u, sizeof(float) ) != sizeof(float) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadFloat: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( &buf.u );
|
||||
@@ -78,7 +94,7 @@ void sgWriteFloat ( gzFile fd, const float var )
|
||||
sgEndianSwap( &buf.u );
|
||||
}
|
||||
if ( gzwrite ( fd, (void *)(&buf.u), sizeof(float) ) != sizeof(float) ) {
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteFloat: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +103,8 @@ void sgReadDouble ( gzFile fd, double *var )
|
||||
{
|
||||
union { double v; uint64_t u; } buf;
|
||||
if ( gzread ( fd, &buf.u, sizeof(double) ) != sizeof(double) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadDouble: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( &buf.u );
|
||||
@@ -104,7 +121,7 @@ void sgWriteDouble ( gzFile fd, const double var )
|
||||
sgEndianSwap( &buf.u );
|
||||
}
|
||||
if ( gzwrite ( fd, (void *)(&buf.u), sizeof(double) ) != sizeof(double) ) {
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteDouble: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +129,8 @@ void sgWriteDouble ( gzFile fd, const double var )
|
||||
void sgReadUInt ( gzFile fd, unsigned int *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(unsigned int) ) != sizeof(unsigned int) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadUInt: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( (uint32_t *)var);
|
||||
@@ -128,7 +146,7 @@ void sgWriteUInt ( gzFile fd, const unsigned int var )
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned int) )
|
||||
!= sizeof(unsigned int) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteUInt: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +154,8 @@ void sgWriteUInt ( gzFile fd, const unsigned int var )
|
||||
void sgReadInt ( gzFile fd, int *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(int) ) != sizeof(int) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadInt: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( (uint32_t *)var);
|
||||
@@ -150,7 +169,7 @@ void sgWriteInt ( gzFile fd, const int var )
|
||||
sgEndianSwap( (uint32_t *)&var);
|
||||
}
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(int) ) != sizeof(int) ) {
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteInt: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +177,8 @@ void sgWriteInt ( gzFile fd, const int var )
|
||||
void sgReadLong ( gzFile fd, int32_t *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(int32_t) ) != sizeof(int32_t) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadLong: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( (uint32_t *)var);
|
||||
@@ -174,7 +194,7 @@ void sgWriteLong ( gzFile fd, const int32_t var )
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(int32_t) )
|
||||
!= sizeof(int32_t) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteLong: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +202,8 @@ void sgWriteLong ( gzFile fd, const int32_t var )
|
||||
void sgReadLongLong ( gzFile fd, int64_t *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(int64_t) ) != sizeof(int64_t) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadLongLong: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( (uint64_t *)var);
|
||||
@@ -198,7 +219,7 @@ void sgWriteLongLong ( gzFile fd, const int64_t var )
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(int64_t) )
|
||||
!= sizeof(int64_t) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteLongLong: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +227,8 @@ void sgWriteLongLong ( gzFile fd, const int64_t var )
|
||||
void sgReadUShort ( gzFile fd, unsigned short *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(unsigned short) ) != sizeof(unsigned short) ){
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadUShort: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( (uint16_t *)var);
|
||||
@@ -222,7 +244,7 @@ void sgWriteUShort ( gzFile fd, const unsigned short var )
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned short) )
|
||||
!= sizeof(unsigned short) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteUShort: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +252,8 @@ void sgWriteUShort ( gzFile fd, const unsigned short var )
|
||||
void sgReadShort ( gzFile fd, short *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(short) ) != sizeof(short) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadShort: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
sgEndianSwap( (uint16_t *)var);
|
||||
@@ -244,7 +267,7 @@ void sgWriteShort ( gzFile fd, const short var )
|
||||
sgEndianSwap( (uint16_t *)&var);
|
||||
}
|
||||
if ( gzwrite ( fd, (void *)(&var), sizeof(short) ) != sizeof(short) ) {
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteShort: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +275,8 @@ void sgWriteShort ( gzFile fd, const short var )
|
||||
void sgReadFloat ( gzFile fd, const unsigned int n, float *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(float) * n ) != (int)(sizeof(float) * n) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadFloat array: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
for ( unsigned int i = 0; i < n; ++i ) {
|
||||
@@ -276,14 +300,15 @@ void sgWriteFloat ( gzFile fd, const unsigned int n, const float *var )
|
||||
if ( gzwrite ( fd, (void *)var, sizeof(float) * n )
|
||||
!= (int)(sizeof(float) * n) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteFloat array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
void sgReadDouble ( gzFile fd, const unsigned int n, double *var )
|
||||
{
|
||||
if ( gzread ( fd, var, sizeof(double) * n ) != (int)(sizeof(double) * n) ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadDouble array: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
for ( unsigned int i = 0; i < n; ++i ) {
|
||||
@@ -307,7 +332,7 @@ void sgWriteDouble ( gzFile fd, const unsigned int n, const double *var )
|
||||
if ( gzwrite ( fd, (void *)var, sizeof(double) * n )
|
||||
!= (int)(sizeof(double) * n) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteDouble array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +340,8 @@ void sgReadBytes ( gzFile fd, const unsigned int n, void *var )
|
||||
{
|
||||
if ( n == 0) return;
|
||||
if ( gzread ( fd, var, n ) != (int)n ) {
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadBytes: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +349,7 @@ void sgWriteBytes ( gzFile fd, const unsigned int n, const void *var )
|
||||
{
|
||||
if ( n == 0) return;
|
||||
if ( gzwrite ( fd, (void *)var, n ) != (int)n ) {
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteBytes: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +359,8 @@ void sgReadUShort ( gzFile fd, const unsigned int n, unsigned short *var )
|
||||
if ( gzread ( fd, var, sizeof(unsigned short) * n )
|
||||
!= (int)(sizeof(unsigned short) * n) )
|
||||
{
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadUShort array: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
for ( unsigned int i = 0; i < n; ++i ) {
|
||||
@@ -357,7 +384,7 @@ void sgWriteUShort ( gzFile fd, const unsigned int n, const unsigned short *var
|
||||
if ( gzwrite ( fd, (void *)var, sizeof(unsigned short) * n )
|
||||
!= (int)(sizeof(unsigned short) * n) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteUShort array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +395,8 @@ void sgReadShort ( gzFile fd, const unsigned int n, short *var )
|
||||
if ( gzread ( fd, var, sizeof(short) * n )
|
||||
!= (int)(sizeof(short) * n) )
|
||||
{
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadShort array: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
for ( unsigned int i = 0; i < n; ++i ) {
|
||||
@@ -392,7 +420,7 @@ void sgWriteShort ( gzFile fd, const unsigned int n, const short *var )
|
||||
if ( gzwrite ( fd, (void *)var, sizeof(short) * n )
|
||||
!= (int)(sizeof(short) * n) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteShort array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +430,8 @@ void sgReadUInt ( gzFile fd, const unsigned int n, unsigned int *var )
|
||||
if ( gzread ( fd, var, sizeof(unsigned int) * n )
|
||||
!= (int)(sizeof(unsigned int) * n) )
|
||||
{
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadUInt array: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
for ( unsigned int i = 0; i < n; ++i ) {
|
||||
@@ -426,7 +455,7 @@ void sgWriteUInt ( gzFile fd, const unsigned int n, const unsigned int *var )
|
||||
if ( gzwrite ( fd, (void *)var, sizeof(unsigned int) * n )
|
||||
!= (int)(sizeof(unsigned int) * n) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteUInt array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +466,8 @@ void sgReadInt ( gzFile fd, const unsigned int n, int *var )
|
||||
if ( gzread ( fd, var, sizeof(int) * n )
|
||||
!= (int)(sizeof(int) * n) )
|
||||
{
|
||||
read_error = true ;
|
||||
throw sg_io_exception("sgReadInt array: GZRead failed:" + gzErrorMessage(fd),
|
||||
sg_location{thread_gzPath}, nullptr, false);
|
||||
}
|
||||
if ( sgIsBigEndian() ) {
|
||||
for ( unsigned int i = 0; i < n; ++i ) {
|
||||
@@ -461,7 +491,7 @@ void sgWriteInt ( gzFile fd, const unsigned int n, const int *var )
|
||||
if ( gzwrite ( fd, (void *)var, sizeof(int) * n )
|
||||
!= (int)(sizeof(int) * n) )
|
||||
{
|
||||
write_error = true ;
|
||||
throw sg_io_exception("sgWriteInt array: gzwrite failed:" + gzErrorMessage(fd), {} /* origin */, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
// forward decls
|
||||
class SGPath;
|
||||
|
||||
// Note that output is written in little endian form (and converted as
|
||||
// necessary for big endian machines)
|
||||
|
||||
@@ -121,9 +124,10 @@ inline void sgWriteGeod ( gzFile fd, const SGGeod& var ) {
|
||||
sgWriteDouble( fd, var.getElevationM() );
|
||||
}
|
||||
|
||||
void sgClearReadError();
|
||||
void sgClearWriteError();
|
||||
int sgReadError();
|
||||
int sgWriteError();
|
||||
/**
|
||||
@ error aid: allow calling code to specify which file path we're reading from, so that erros we
|
||||
throw from sgReadXXXX can have a valid location set.
|
||||
*/
|
||||
void setThreadLocalSimgearReadPath(const SGPath& path);
|
||||
|
||||
#endif // _SG_LOWLEVEL_HXX
|
||||
|
||||
@@ -41,8 +41,10 @@
|
||||
#include <bitset>
|
||||
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/math/SGGeometry.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "lowlevel.hxx"
|
||||
@@ -453,10 +455,6 @@ void SGBinObject::read_object( gzFile fp,
|
||||
}
|
||||
}
|
||||
|
||||
if ( sgReadError() ) {
|
||||
throw sg_exception("Error reading object properties");
|
||||
}
|
||||
|
||||
size_t indexCount = std::bitset<32>((int)idx_mask).count();
|
||||
if (indexCount == 0) {
|
||||
throw sg_exception("object index mask has no bits set");
|
||||
@@ -464,18 +462,10 @@ void SGBinObject::read_object( gzFile fp,
|
||||
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
if ( sgReadError() ) {
|
||||
throw sg_exception("Error reading element size");
|
||||
}
|
||||
|
||||
buf.resize( nbytes );
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
|
||||
if ( sgReadError() ) {
|
||||
throw sg_exception("Error reading element bytes");
|
||||
}
|
||||
|
||||
int_list vs;
|
||||
int_list ns;
|
||||
int_list cs;
|
||||
@@ -502,306 +492,311 @@ void SGBinObject::read_object( gzFile fp,
|
||||
|
||||
|
||||
// read a binary file and populate the provided structures.
|
||||
bool SGBinObject::read_bin( const SGPath& file ) {
|
||||
SGVec3d p;
|
||||
int i, k;
|
||||
size_t j;
|
||||
unsigned int nbytes;
|
||||
sgSimpleBuffer buf( 32768 ); // 32 Kb
|
||||
bool SGBinObject::read_bin( const SGPath& file )
|
||||
{
|
||||
gzFile fp = NULL;
|
||||
|
||||
try {
|
||||
SGVec3d p;
|
||||
int i, k;
|
||||
size_t j;
|
||||
unsigned int nbytes;
|
||||
sgSimpleBuffer buf( 32768 ); // 32 Kb
|
||||
|
||||
// zero out structures
|
||||
gbs_center = SGVec3d(0, 0, 0);
|
||||
gbs_radius = 0.0;
|
||||
simgear::ErrorReportContext ec("btg", file.utf8Str());
|
||||
|
||||
wgs84_nodes.clear();
|
||||
normals.clear();
|
||||
texcoords.clear();
|
||||
// zero out structures
|
||||
gbs_center = SGVec3d(0, 0, 0);
|
||||
gbs_radius = 0.0;
|
||||
|
||||
pts_v.clear();
|
||||
pts_n.clear();
|
||||
pts_c.clear();
|
||||
pts_tcs.clear();
|
||||
pts_vas.clear();
|
||||
pt_materials.clear();
|
||||
wgs84_nodes.clear();
|
||||
normals.clear();
|
||||
texcoords.clear();
|
||||
|
||||
tris_v.clear();
|
||||
tris_n.clear();
|
||||
tris_c.clear();
|
||||
tris_tcs.clear();
|
||||
tris_vas.clear();
|
||||
tri_materials.clear();
|
||||
pts_v.clear();
|
||||
pts_n.clear();
|
||||
pts_c.clear();
|
||||
pts_tcs.clear();
|
||||
pts_vas.clear();
|
||||
pt_materials.clear();
|
||||
|
||||
strips_v.clear();
|
||||
strips_n.clear();
|
||||
strips_c.clear();
|
||||
strips_tcs.clear();
|
||||
strips_vas.clear();
|
||||
strip_materials.clear();
|
||||
tris_v.clear();
|
||||
tris_n.clear();
|
||||
tris_c.clear();
|
||||
tris_tcs.clear();
|
||||
tris_vas.clear();
|
||||
tri_materials.clear();
|
||||
|
||||
fans_v.clear();
|
||||
fans_n.clear();
|
||||
fans_c.clear();
|
||||
fans_tcs.clear();
|
||||
fans_vas.clear();
|
||||
fan_materials.clear();
|
||||
strips_v.clear();
|
||||
strips_n.clear();
|
||||
strips_c.clear();
|
||||
strips_tcs.clear();
|
||||
strips_vas.clear();
|
||||
strip_materials.clear();
|
||||
|
||||
gzFile fp = gzFileFromSGPath(file, "rb");
|
||||
if ( fp == NULL ) {
|
||||
SGPath withGZ = file;
|
||||
withGZ.concat(".gz");
|
||||
fp = gzFileFromSGPath(withGZ, "rb");
|
||||
if (fp == nullptr) {
|
||||
SG_LOG( SG_EVENT, SG_ALERT,
|
||||
"ERROR: opening " << file << " or " << withGZ << " for reading!");
|
||||
fans_v.clear();
|
||||
fans_n.clear();
|
||||
fans_c.clear();
|
||||
fans_tcs.clear();
|
||||
fans_vas.clear();
|
||||
fan_materials.clear();
|
||||
|
||||
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file));
|
||||
gzFile fp = gzFileFromSGPath(file, "rb");
|
||||
if ( fp == NULL ) {
|
||||
SGPath withGZ = file;
|
||||
withGZ.concat(".gz");
|
||||
fp = gzFileFromSGPath(withGZ, "rb");
|
||||
if (fp == nullptr) {
|
||||
throw sg_io_exception("Error opening for reading (and .gz)", sg_location(file), {}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setThreadLocalSimgearReadPath(file);
|
||||
|
||||
sgClearReadError();
|
||||
// read headers
|
||||
unsigned int header;
|
||||
sgReadUInt( fp, &header );
|
||||
|
||||
// read headers
|
||||
unsigned int header;
|
||||
sgReadUInt( fp, &header );
|
||||
if ( ((header & 0xFF000000) >> 24) == 'S' &&
|
||||
((header & 0x00FF0000) >> 16) == 'G' ) {
|
||||
|
||||
// read file version
|
||||
version = (header & 0x0000FFFF);
|
||||
} else {
|
||||
// close the file before we return
|
||||
gzclose(fp);
|
||||
throw sg_io_exception("Bad BTG magic/version", sg_location(file));
|
||||
}
|
||||
if ( ((header & 0xFF000000) >> 24) == 'S' &&
|
||||
((header & 0x00FF0000) >> 16) == 'G' ) {
|
||||
|
||||
// read creation time
|
||||
unsigned int foo_calendar_time;
|
||||
sgReadUInt( fp, &foo_calendar_time );
|
||||
// read file version
|
||||
version = (header & 0x0000FFFF);
|
||||
} else {
|
||||
throw sg_io_exception("Bad BTG magic/version", sg_location(file), {}, false);
|
||||
}
|
||||
|
||||
#if 0
|
||||
time_t calendar_time = foo_calendar_time;
|
||||
// The following code has a global effect on the host application
|
||||
// and can screws up the time elsewhere. It should be avoided
|
||||
// unless you need this for debugging in which case you should
|
||||
// disable it again once the debugging task is finished.
|
||||
struct tm *local_tm;
|
||||
local_tm = localtime( &calendar_time );
|
||||
char time_str[256];
|
||||
strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
|
||||
SG_LOG( SG_EVENT, SG_DEBUG, "File created on " << time_str);
|
||||
#endif
|
||||
// read creation time
|
||||
unsigned int foo_calendar_time;
|
||||
sgReadUInt( fp, &foo_calendar_time );
|
||||
|
||||
// read number of top level objects
|
||||
int nobjects;
|
||||
if ( version >= 10) { // version 10 extends everything to be 32-bit
|
||||
sgReadInt( fp, &nobjects );
|
||||
} else if ( version >= 7 ) {
|
||||
uint16_t v;
|
||||
sgReadUShort( fp, &v );
|
||||
nobjects = v;
|
||||
} else {
|
||||
int16_t v;
|
||||
sgReadShort( fp, &v );
|
||||
nobjects = v;
|
||||
}
|
||||
#if 0
|
||||
time_t calendar_time = foo_calendar_time;
|
||||
// The following code has a global effect on the host application
|
||||
// and can screws up the time elsewhere. It should be avoided
|
||||
// unless you need this for debugging in which case you should
|
||||
// disable it again once the debugging task is finished.
|
||||
struct tm *local_tm;
|
||||
local_tm = localtime( &calendar_time );
|
||||
char time_str[256];
|
||||
strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
|
||||
SG_LOG( SG_EVENT, SG_DEBUG, "File created on " << time_str);
|
||||
#endif
|
||||
|
||||
SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin Total objects to read = " << nobjects);
|
||||
|
||||
if ( sgReadError() ) {
|
||||
throw sg_io_exception("Error reading BTG file header", sg_location(file));
|
||||
}
|
||||
|
||||
// read in objects
|
||||
for ( i = 0; i < nobjects; ++i ) {
|
||||
// read object header
|
||||
char obj_type;
|
||||
uint32_t nproperties, nelements;
|
||||
sgReadChar( fp, &obj_type );
|
||||
if ( version >= 10 ) {
|
||||
sgReadUInt( fp, &nproperties );
|
||||
sgReadUInt( fp, &nelements );
|
||||
// read number of top level objects
|
||||
int nobjects;
|
||||
if ( version >= 10) { // version 10 extends everything to be 32-bit
|
||||
sgReadInt( fp, &nobjects );
|
||||
} else if ( version >= 7 ) {
|
||||
uint16_t v;
|
||||
sgReadUShort( fp, &v );
|
||||
nproperties = v;
|
||||
sgReadUShort( fp, &v );
|
||||
nelements = v;
|
||||
nobjects = v;
|
||||
} else {
|
||||
int16_t v;
|
||||
sgReadShort( fp, &v );
|
||||
nproperties = v;
|
||||
sgReadShort( fp, &v );
|
||||
nelements = v;
|
||||
nobjects = v;
|
||||
}
|
||||
|
||||
SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin object " << i <<
|
||||
" = " << (int)obj_type << " props = " << nproperties <<
|
||||
" elements = " << nelements);
|
||||
SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin Total objects to read = " << nobjects);
|
||||
|
||||
if ( obj_type == SG_BOUNDING_SPHERE ) {
|
||||
// read bounding sphere properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read bounding sphere elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
gbs_center = buf.readVec3d();
|
||||
gbs_radius = buf.readFloat();
|
||||
// read in objects
|
||||
for ( i = 0; i < nobjects; ++i ) {
|
||||
// read object header
|
||||
char obj_type;
|
||||
uint32_t nproperties, nelements;
|
||||
sgReadChar( fp, &obj_type );
|
||||
if ( version >= 10 ) {
|
||||
sgReadUInt( fp, &nproperties );
|
||||
sgReadUInt( fp, &nelements );
|
||||
} else if ( version >= 7 ) {
|
||||
uint16_t v;
|
||||
sgReadUShort( fp, &v );
|
||||
nproperties = v;
|
||||
sgReadUShort( fp, &v );
|
||||
nelements = v;
|
||||
} else {
|
||||
int16_t v;
|
||||
sgReadShort( fp, &v );
|
||||
nproperties = v;
|
||||
sgReadShort( fp, &v );
|
||||
nelements = v;
|
||||
}
|
||||
} else if ( obj_type == SG_VERTEX_LIST ) {
|
||||
// read vertex list properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read vertex list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float) * 3);
|
||||
wgs84_nodes.reserve( count );
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
SGVec3f v = buf.readVec3f();
|
||||
// extend from float to double, hmmm
|
||||
wgs84_nodes.push_back( SGVec3d(v[0], v[1], v[2]) );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "SGBinObject::read_bin object " << i <<
|
||||
" = " << (int)obj_type << " props = " << nproperties <<
|
||||
" elements = " << nelements);
|
||||
|
||||
if ( obj_type == SG_BOUNDING_SPHERE ) {
|
||||
// read bounding sphere properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read bounding sphere elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
gbs_center = buf.readVec3d();
|
||||
gbs_radius = buf.readFloat();
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_COLOR_LIST ) {
|
||||
// read color list properties
|
||||
read_properties( fp, nproperties );
|
||||
} else if ( obj_type == SG_VERTEX_LIST ) {
|
||||
// read vertex list properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read color list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float) * 4);
|
||||
colors.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
colors.push_back( buf.readVec4f() );
|
||||
// read vertex list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float) * 3);
|
||||
wgs84_nodes.reserve( count );
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
SGVec3f v = buf.readVec3f();
|
||||
// extend from float to double, hmmm
|
||||
wgs84_nodes.push_back( SGVec3d(v[0], v[1], v[2]) );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_NORMAL_LIST ) {
|
||||
// read normal list properties
|
||||
read_properties( fp, nproperties );
|
||||
} else if ( obj_type == SG_COLOR_LIST ) {
|
||||
// read color list properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read normal list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
unsigned char *ptr = (unsigned char *)(buf.get_ptr());
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / 3;
|
||||
normals.reserve( count );
|
||||
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
SGVec3f normal( (ptr[0]) / 127.5 - 1.0,
|
||||
(ptr[1]) / 127.5 - 1.0,
|
||||
(ptr[2]) / 127.5 - 1.0);
|
||||
normals.push_back(normalize(normal));
|
||||
ptr += 3;
|
||||
// read color list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float) * 4);
|
||||
colors.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
colors.push_back( buf.readVec4f() );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_TEXCOORD_LIST ) {
|
||||
// read texcoord list properties
|
||||
read_properties( fp, nproperties );
|
||||
} else if ( obj_type == SG_NORMAL_LIST ) {
|
||||
// read normal list properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read texcoord list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float) * 2);
|
||||
texcoords.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
texcoords.push_back( buf.readVec2f() );
|
||||
// read normal list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
unsigned char *ptr = (unsigned char *)(buf.get_ptr());
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / 3;
|
||||
normals.reserve( count );
|
||||
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
SGVec3f normal( (ptr[0]) / 127.5 - 1.0,
|
||||
(ptr[1]) / 127.5 - 1.0,
|
||||
(ptr[2]) / 127.5 - 1.0);
|
||||
normals.push_back(normalize(normal));
|
||||
ptr += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_VA_FLOAT_LIST ) {
|
||||
// read vertex attribute (float) properties
|
||||
read_properties( fp, nproperties );
|
||||
} else if ( obj_type == SG_TEXCOORD_LIST ) {
|
||||
// read texcoord list properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read vertex attribute list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float));
|
||||
va_flt.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
va_flt.push_back( buf.readFloat() );
|
||||
// read texcoord list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float) * 2);
|
||||
texcoords.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
texcoords.push_back( buf.readVec2f() );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_VA_INTEGER_LIST ) {
|
||||
// read vertex attribute (integer) properties
|
||||
read_properties( fp, nproperties );
|
||||
} else if ( obj_type == SG_VA_FLOAT_LIST ) {
|
||||
// read vertex attribute (float) properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read vertex attribute list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(unsigned int));
|
||||
va_int.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
va_int.push_back( buf.readInt() );
|
||||
// read vertex attribute list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(float));
|
||||
va_flt.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
va_flt.push_back( buf.readFloat() );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_POINTS ) {
|
||||
// read point elements
|
||||
read_object( fp, SG_POINTS, nproperties, nelements,
|
||||
pts_v, pts_n, pts_c, pts_tcs,
|
||||
pts_vas, pt_materials );
|
||||
} else if ( obj_type == SG_TRIANGLE_FACES ) {
|
||||
// read triangle face properties
|
||||
read_object( fp, SG_TRIANGLE_FACES, nproperties, nelements,
|
||||
tris_v, tris_n, tris_c, tris_tcs,
|
||||
tris_vas, tri_materials );
|
||||
} else if ( obj_type == SG_TRIANGLE_STRIPS ) {
|
||||
// read triangle strip properties
|
||||
read_object( fp, SG_TRIANGLE_STRIPS, nproperties, nelements,
|
||||
strips_v, strips_n, strips_c, strips_tcs,
|
||||
strips_vas, strip_materials );
|
||||
} else if ( obj_type == SG_TRIANGLE_FANS ) {
|
||||
// read triangle fan properties
|
||||
read_object( fp, SG_TRIANGLE_FANS, nproperties, nelements,
|
||||
fans_v, fans_n, fans_c, fans_tcs,
|
||||
fans_vas, fan_materials );
|
||||
} else {
|
||||
// unknown object type, just skip
|
||||
read_properties( fp, nproperties );
|
||||
} else if ( obj_type == SG_VA_INTEGER_LIST ) {
|
||||
// read vertex attribute (integer) properties
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
// cout << "element size = " << nbytes << endl;
|
||||
if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
// read vertex attribute list elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
buf.resize( nbytes );
|
||||
buf.reset();
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
int count = nbytes / (sizeof(unsigned int));
|
||||
va_int.reserve(count);
|
||||
for ( k = 0; k < count; ++k ) {
|
||||
va_int.push_back( buf.readInt() );
|
||||
}
|
||||
}
|
||||
} else if ( obj_type == SG_POINTS ) {
|
||||
// read point elements
|
||||
read_object( fp, SG_POINTS, nproperties, nelements,
|
||||
pts_v, pts_n, pts_c, pts_tcs,
|
||||
pts_vas, pt_materials );
|
||||
} else if ( obj_type == SG_TRIANGLE_FACES ) {
|
||||
// read triangle face properties
|
||||
read_object( fp, SG_TRIANGLE_FACES, nproperties, nelements,
|
||||
tris_v, tris_n, tris_c, tris_tcs,
|
||||
tris_vas, tri_materials );
|
||||
} else if ( obj_type == SG_TRIANGLE_STRIPS ) {
|
||||
// read triangle strip properties
|
||||
read_object( fp, SG_TRIANGLE_STRIPS, nproperties, nelements,
|
||||
strips_v, strips_n, strips_c, strips_tcs,
|
||||
strips_vas, strip_materials );
|
||||
} else if ( obj_type == SG_TRIANGLE_FANS ) {
|
||||
// read triangle fan properties
|
||||
read_object( fp, SG_TRIANGLE_FANS, nproperties, nelements,
|
||||
fans_v, fans_n, fans_c, fans_tcs,
|
||||
fans_vas, fan_materials );
|
||||
} else {
|
||||
// unknown object type, just skip
|
||||
read_properties( fp, nproperties );
|
||||
|
||||
// read elements
|
||||
for ( j = 0; j < nelements; ++j ) {
|
||||
sgReadUInt( fp, &nbytes );
|
||||
// cout << "element size = " << nbytes << endl;
|
||||
if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
|
||||
char *ptr = buf.get_ptr();
|
||||
sgReadBytes( fp, nbytes, ptr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( sgReadError() ) {
|
||||
throw sg_io_exception("Error while reading object", sg_location(file, i));
|
||||
gzclose(fp);
|
||||
fp = NULL;
|
||||
} catch (std::exception&) {
|
||||
if (fp) {
|
||||
// close the file
|
||||
gzclose(fp);
|
||||
}
|
||||
|
||||
throw; // re-throw
|
||||
}
|
||||
|
||||
// close the file
|
||||
gzclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -975,112 +970,112 @@ bool SGBinObject::write_bin_file(const SGPath& file)
|
||||
return false;
|
||||
}
|
||||
|
||||
sgClearWriteError();
|
||||
try {
|
||||
SG_LOG(SG_IO, SG_DEBUG, "points size = " << pts_v.size()
|
||||
<< " pt_materials = " << pt_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "triangles size = " << tris_v.size()
|
||||
<< " tri_materials = " << tri_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "strips size = " << strips_v.size()
|
||||
<< " strip_materials = " << strip_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "fans size = " << fans_v.size()
|
||||
<< " fan_materials = " << fan_materials.size() );
|
||||
|
||||
SG_LOG(SG_IO, SG_DEBUG, "points size = " << pts_v.size()
|
||||
<< " pt_materials = " << pt_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "triangles size = " << tris_v.size()
|
||||
<< " tri_materials = " << tri_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "strips size = " << strips_v.size()
|
||||
<< " strip_materials = " << strip_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "fans size = " << fans_v.size()
|
||||
<< " fan_materials = " << fan_materials.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "nodes = " << wgs84_nodes.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "colors = " << colors.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "normals = " << normals.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "tex coords = " << texcoords.size() );
|
||||
|
||||
SG_LOG(SG_IO, SG_DEBUG, "nodes = " << wgs84_nodes.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "colors = " << colors.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "normals = " << normals.size() );
|
||||
SG_LOG(SG_IO, SG_DEBUG, "tex coords = " << texcoords.size() );
|
||||
version = 10;
|
||||
bool shortMaterialsRanges =
|
||||
(max_object_size(pt_materials) < VERSION_7_MATERIAL_LIMIT) &&
|
||||
(max_object_size(fan_materials) < VERSION_7_MATERIAL_LIMIT) &&
|
||||
(max_object_size(strip_materials) < VERSION_7_MATERIAL_LIMIT) &&
|
||||
(max_object_size(tri_materials) < VERSION_7_MATERIAL_LIMIT);
|
||||
|
||||
version = 10;
|
||||
bool shortMaterialsRanges =
|
||||
(max_object_size(pt_materials) < VERSION_7_MATERIAL_LIMIT) &&
|
||||
(max_object_size(fan_materials) < VERSION_7_MATERIAL_LIMIT) &&
|
||||
(max_object_size(strip_materials) < VERSION_7_MATERIAL_LIMIT) &&
|
||||
(max_object_size(tri_materials) < VERSION_7_MATERIAL_LIMIT);
|
||||
if ((wgs84_nodes.size() < 0xffff) &&
|
||||
(normals.size() < 0xffff) &&
|
||||
(texcoords.size() < 0xffff) &&
|
||||
shortMaterialsRanges) {
|
||||
version = 7; // use smaller indices if possible
|
||||
}
|
||||
|
||||
if ((wgs84_nodes.size() < 0xffff) &&
|
||||
(normals.size() < 0xffff) &&
|
||||
(texcoords.size() < 0xffff) &&
|
||||
shortMaterialsRanges) {
|
||||
version = 7; // use smaller indices if possible
|
||||
}
|
||||
// write header magic
|
||||
|
||||
// write header magic
|
||||
/** Magic Number for our file format */
|
||||
#define SG_FILE_MAGIC_NUMBER ( ('S'<<24) + ('G'<<16) + version )
|
||||
|
||||
/** Magic Number for our file format */
|
||||
#define SG_FILE_MAGIC_NUMBER ( ('S'<<24) + ('G'<<16) + version )
|
||||
sgWriteUInt( fp, SG_FILE_MAGIC_NUMBER );
|
||||
time_t calendar_time = time(NULL);
|
||||
sgWriteLong( fp, (int32_t)calendar_time );
|
||||
|
||||
sgWriteUInt( fp, SG_FILE_MAGIC_NUMBER );
|
||||
time_t calendar_time = time(NULL);
|
||||
sgWriteLong( fp, (int32_t)calendar_time );
|
||||
// calculate and write number of top level objects
|
||||
int nobjects = 5; // gbs, vertices, colors, normals, texcoords
|
||||
nobjects += count_objects(pt_materials);
|
||||
nobjects += count_objects(tri_materials);
|
||||
nobjects += count_objects(strip_materials);
|
||||
nobjects += count_objects(fan_materials);
|
||||
|
||||
// calculate and write number of top level objects
|
||||
int nobjects = 5; // gbs, vertices, colors, normals, texcoords
|
||||
nobjects += count_objects(pt_materials);
|
||||
nobjects += count_objects(tri_materials);
|
||||
nobjects += count_objects(strip_materials);
|
||||
nobjects += count_objects(fan_materials);
|
||||
SG_LOG(SG_IO, SG_DEBUG, "total top level objects = " << nobjects);
|
||||
|
||||
SG_LOG(SG_IO, SG_DEBUG, "total top level objects = " << nobjects);
|
||||
if (version == 7) {
|
||||
sgWriteUShort( fp, (uint16_t) nobjects );
|
||||
} else {
|
||||
sgWriteInt( fp, nobjects );
|
||||
}
|
||||
|
||||
if (version == 7) {
|
||||
sgWriteUShort( fp, (uint16_t) nobjects );
|
||||
} else {
|
||||
sgWriteInt( fp, nobjects );
|
||||
}
|
||||
// write bounding sphere
|
||||
write_header( fp, SG_BOUNDING_SPHERE, 0, 1);
|
||||
sgWriteUInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes
|
||||
sgWritedVec3( fp, gbs_center );
|
||||
sgWriteFloat( fp, gbs_radius );
|
||||
|
||||
// write bounding sphere
|
||||
write_header( fp, SG_BOUNDING_SPHERE, 0, 1);
|
||||
sgWriteUInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes
|
||||
sgWritedVec3( fp, gbs_center );
|
||||
sgWriteFloat( fp, gbs_radius );
|
||||
// dump vertex list
|
||||
write_header( fp, SG_VERTEX_LIST, 0, 1);
|
||||
sgWriteUInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes
|
||||
for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
|
||||
sgWriteVec3( fp, toVec3f(wgs84_nodes[i] - gbs_center));
|
||||
}
|
||||
|
||||
// dump vertex list
|
||||
write_header( fp, SG_VERTEX_LIST, 0, 1);
|
||||
sgWriteUInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes
|
||||
for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
|
||||
sgWriteVec3( fp, toVec3f(wgs84_nodes[i] - gbs_center));
|
||||
}
|
||||
// dump vertex color list
|
||||
write_header( fp, SG_COLOR_LIST, 0, 1);
|
||||
sgWriteUInt( fp, colors.size() * sizeof(float) * 4 ); // nbytes
|
||||
for ( i = 0; i < (int)colors.size(); ++i ) {
|
||||
sgWriteVec4( fp, colors[i]);
|
||||
}
|
||||
|
||||
// dump vertex color list
|
||||
write_header( fp, SG_COLOR_LIST, 0, 1);
|
||||
sgWriteUInt( fp, colors.size() * sizeof(float) * 4 ); // nbytes
|
||||
for ( i = 0; i < (int)colors.size(); ++i ) {
|
||||
sgWriteVec4( fp, colors[i]);
|
||||
}
|
||||
// dump vertex normal list
|
||||
write_header( fp, SG_NORMAL_LIST, 0, 1);
|
||||
sgWriteUInt( fp, normals.size() * 3 ); // nbytes
|
||||
char normal[3];
|
||||
for ( i = 0; i < (int)normals.size(); ++i ) {
|
||||
SGVec3f p = normals[i];
|
||||
normal[0] = (unsigned char)((p.x() + 1.0) * 127.5);
|
||||
normal[1] = (unsigned char)((p.y() + 1.0) * 127.5);
|
||||
normal[2] = (unsigned char)((p.z() + 1.0) * 127.5);
|
||||
sgWriteBytes( fp, 3, normal );
|
||||
}
|
||||
|
||||
// dump vertex normal list
|
||||
write_header( fp, SG_NORMAL_LIST, 0, 1);
|
||||
sgWriteUInt( fp, normals.size() * 3 ); // nbytes
|
||||
char normal[3];
|
||||
for ( i = 0; i < (int)normals.size(); ++i ) {
|
||||
SGVec3f p = normals[i];
|
||||
normal[0] = (unsigned char)((p.x() + 1.0) * 127.5);
|
||||
normal[1] = (unsigned char)((p.y() + 1.0) * 127.5);
|
||||
normal[2] = (unsigned char)((p.z() + 1.0) * 127.5);
|
||||
sgWriteBytes( fp, 3, normal );
|
||||
}
|
||||
// dump texture coordinates
|
||||
write_header( fp, SG_TEXCOORD_LIST, 0, 1);
|
||||
sgWriteUInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes
|
||||
for ( i = 0; i < (int)texcoords.size(); ++i ) {
|
||||
sgWriteVec2( fp, texcoords[i]);
|
||||
}
|
||||
|
||||
// dump texture coordinates
|
||||
write_header( fp, SG_TEXCOORD_LIST, 0, 1);
|
||||
sgWriteUInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes
|
||||
for ( i = 0; i < (int)texcoords.size(); ++i ) {
|
||||
sgWriteVec2( fp, texcoords[i]);
|
||||
}
|
||||
write_objects(fp, SG_POINTS, pts_v, pts_n, pts_c, pts_tcs, pts_vas, pt_materials);
|
||||
write_objects(fp, SG_TRIANGLE_FACES, tris_v, tris_n, tris_c, tris_tcs, tris_vas, tri_materials);
|
||||
write_objects(fp, SG_TRIANGLE_STRIPS, strips_v, strips_n, strips_c, strips_tcs, strips_vas, strip_materials);
|
||||
write_objects(fp, SG_TRIANGLE_FANS, fans_v, fans_n, fans_c, fans_tcs, fans_vas, fan_materials);
|
||||
|
||||
write_objects(fp, SG_POINTS, pts_v, pts_n, pts_c, pts_tcs, pts_vas, pt_materials);
|
||||
write_objects(fp, SG_TRIANGLE_FACES, tris_v, tris_n, tris_c, tris_tcs, tris_vas, tri_materials);
|
||||
write_objects(fp, SG_TRIANGLE_STRIPS, strips_v, strips_n, strips_c, strips_tcs, strips_vas, strip_materials);
|
||||
write_objects(fp, SG_TRIANGLE_FANS, fans_v, fans_n, fans_c, fans_tcs, fans_vas, fan_materials);
|
||||
|
||||
// close the file
|
||||
gzclose(fp);
|
||||
|
||||
if ( sgWriteError() ) {
|
||||
cout << "Error while writing file " << file << endl;
|
||||
// close the file
|
||||
gzclose(fp);
|
||||
fp = NULL;
|
||||
} catch (std::exception&) {
|
||||
if (fp) {
|
||||
gzclose(fp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,21 +71,31 @@ SGFile::~SGFile() {
|
||||
std::string SGFile::computeHash()
|
||||
{
|
||||
if (!file_name.exists())
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
simgear::sha1nfo info;
|
||||
sha1_init(&info);
|
||||
char* buf = static_cast<char*>(malloc(1024 * 1024));
|
||||
|
||||
// unique_ptr with custom deleter for exception safety
|
||||
const int bufSize = 1024 * 1024;
|
||||
std::unique_ptr<char, std::function<void(char*)>> buf{static_cast<char*>(malloc(bufSize)),
|
||||
[](char* p) { free(p); }};
|
||||
|
||||
if (!buf) {
|
||||
throw sg_exception("Malloc of SHA buffer failed", {}, file_name);
|
||||
}
|
||||
|
||||
size_t readLen;
|
||||
SGBinaryFile f(file_name);
|
||||
if (!f.open(SG_IO_IN)) {
|
||||
throw sg_io_exception("Couldn't open file for compute hash", file_name);
|
||||
SG_LOG(SG_IO, SG_ALERT, "SGFile::computeHash: Failed to open " << file_name);
|
||||
return {};
|
||||
}
|
||||
while ((readLen = f.read(buf, 1024 * 1024)) > 0) {
|
||||
sha1_write(&info, buf, readLen);
|
||||
while ((readLen = f.read(buf.get(), bufSize)) > 0) {
|
||||
sha1_write(&info, buf.get(), readLen);
|
||||
}
|
||||
|
||||
f.close();
|
||||
free(buf);
|
||||
std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH);
|
||||
return simgear::strutils::encodeHex(hashBytes);
|
||||
}
|
||||
|
||||
BIN
simgear/io/test.tar.xz
Normal file
BIN
simgear/io/test.tar.xz
Normal file
Binary file not shown.
@@ -1,16 +1,18 @@
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <simgear/simgear_config.h>
|
||||
|
||||
#include "test_HTTP.hxx"
|
||||
#include "HTTPRepository.hxx"
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPRepository.hxx"
|
||||
#include "HTTPTestApi_private.hxx"
|
||||
#include "test_HTTP.hxx"
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/misc/sg_hash.hxx>
|
||||
@@ -25,8 +27,14 @@
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
using TestApi = simgear::HTTP::TestApi;
|
||||
|
||||
std::string dataForFile(const std::string& parentName, const std::string& name, int revision)
|
||||
{
|
||||
if (name == "zeroByteFile") {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::ostringstream os;
|
||||
// random content but which definitely depends on our tree location
|
||||
// and revision.
|
||||
@@ -45,6 +53,9 @@ std::string hashForData(const std::string& d)
|
||||
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
|
||||
}
|
||||
|
||||
class TestRepoEntry;
|
||||
using AccessCallback = std::function<void(TestRepoEntry &entry)>;
|
||||
|
||||
class TestRepoEntry
|
||||
{
|
||||
public:
|
||||
@@ -70,7 +81,8 @@ public:
|
||||
int requestCount;
|
||||
bool getWillFail;
|
||||
bool returnCorruptData;
|
||||
std::unique_ptr<SGCallback> accessCallback;
|
||||
|
||||
AccessCallback accessCallback;
|
||||
|
||||
void clearRequestCounts();
|
||||
|
||||
@@ -270,8 +282,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->accessCallback.get()) {
|
||||
(*entry->accessCallback)();
|
||||
if (entry->accessCallback) {
|
||||
entry->accessCallback(*entry);
|
||||
}
|
||||
|
||||
if (entry->getWillFail) {
|
||||
@@ -282,20 +294,29 @@ public:
|
||||
entry->requestCount++;
|
||||
|
||||
std::string content;
|
||||
bool closeSocket = false;
|
||||
size_t contentSize = 0;
|
||||
|
||||
if (entry->returnCorruptData) {
|
||||
content = dataForFile("!$£$!" + entry->parent->name,
|
||||
"corrupt_" + entry->name,
|
||||
entry->revision);
|
||||
contentSize = content.size();
|
||||
} else {
|
||||
content = entry->data();
|
||||
content = entry->data();
|
||||
contentSize = content.size();
|
||||
}
|
||||
|
||||
std::stringstream d;
|
||||
d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
|
||||
d << "Content-Length:" << content.size() << "\r\n";
|
||||
d << "Content-Length:" << contentSize << "\r\n";
|
||||
d << "\r\n"; // final CRLF to terminate the headers
|
||||
d << content;
|
||||
push(d.str().c_str());
|
||||
|
||||
if (closeSocket) {
|
||||
closeWhenDone();
|
||||
}
|
||||
} else {
|
||||
sendErrorResponse(404, false, "");
|
||||
}
|
||||
@@ -392,6 +413,7 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
|
||||
cl->update();
|
||||
testServer.poll();
|
||||
|
||||
repo->process();
|
||||
if (!repo->isDoingSync()) {
|
||||
return;
|
||||
}
|
||||
@@ -401,6 +423,16 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
|
||||
std::cerr << "timed out" << std::endl;
|
||||
}
|
||||
|
||||
void runForTime(HTTP::Client *cl, HTTPRepository *repo, int msec = 15) {
|
||||
SGTimeStamp start(SGTimeStamp::now());
|
||||
while (start.elapsedMSec() < msec) {
|
||||
cl->update();
|
||||
testServer.poll();
|
||||
repo->process();
|
||||
SGTimeStamp::sleepForMSec(1);
|
||||
}
|
||||
}
|
||||
|
||||
void testBasicClone(HTTP::Client* cl)
|
||||
{
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
@@ -418,6 +450,7 @@ void testBasicClone(HTTP::Client* cl)
|
||||
verifyFileState(p, "fileA");
|
||||
verifyFileState(p, "dirA/subdirA/fileAAA");
|
||||
verifyFileState(p, "dirC/subdirA/subsubA/fileCAAA");
|
||||
verifyFileState(p, "dirA/subdirA/zeroByteFile");
|
||||
|
||||
global_repo->findEntry("fileA")->revision++;
|
||||
global_repo->findEntry("dirB/subdirA/fileBAA")->revision++;
|
||||
@@ -618,9 +651,19 @@ void testAbandonCorruptFiles(HTTP::Client* cl)
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) {
|
||||
std::cerr << "Got failure state:" << repo->failure() << std::endl;
|
||||
throw sg_exception("Bad result from corrupt files test");
|
||||
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
|
||||
std::cerr << "Got failure state:" << repo->failure() << std::endl;
|
||||
throw sg_exception("Bad result from corrupt files test");
|
||||
}
|
||||
|
||||
auto failedFiles = repo->failures();
|
||||
if (failedFiles.size() != 1) {
|
||||
throw sg_exception("Bad result from corrupt files test");
|
||||
}
|
||||
|
||||
if (failedFiles.front().path.utf8Str() != "dirB/subdirG/fileBGA") {
|
||||
throw sg_exception("Bad path from corrupt files test:" +
|
||||
failedFiles.front().path.utf8Str());
|
||||
}
|
||||
|
||||
repo.reset();
|
||||
@@ -657,15 +700,21 @@ void testServerModifyDuringSync(HTTP::Client* cl)
|
||||
repo.reset(new HTTPRepository(p, cl));
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback.reset(make_callback(&modifyBTree));
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback =
|
||||
[](const TestRepoEntry &r) {
|
||||
std::cout << "Modifying sub-tree" << std::endl;
|
||||
global_repo->findEntry("dirB/subdirA/fileBAC")->revision++;
|
||||
global_repo->defineFile("dirB/subdirZ/fileBZA");
|
||||
global_repo->findEntry("dirB/subdirB/fileBBB")->revision++;
|
||||
};
|
||||
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback.reset();
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback = AccessCallback{};
|
||||
|
||||
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) {
|
||||
throw sg_exception("Bad result from modify during sync test");
|
||||
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
|
||||
throw sg_exception("Bad result from modify during sync test");
|
||||
}
|
||||
|
||||
std::cout << "Passed test modify server during sync" << std::endl;
|
||||
@@ -755,6 +804,124 @@ void testCopyInstalledChildren(HTTP::Client* cl)
|
||||
std::cout << "passed Copy installed children" << std::endl;
|
||||
}
|
||||
|
||||
void testRetryAfterSocketFailure(HTTP::Client *cl) {
|
||||
global_repo->clearRequestCounts();
|
||||
global_repo->clearFailFlags();
|
||||
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
SGPath p(simgear::Dir::current().path());
|
||||
p.append("http_repo_retry_after_socket_fail");
|
||||
simgear::Dir pd(p);
|
||||
if (pd.exists()) {
|
||||
pd.removeChildren();
|
||||
}
|
||||
|
||||
repo.reset(new HTTPRepository(p, cl));
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
|
||||
int aaFailsRemaining = 2;
|
||||
int subdirBAFailsRemaining = 2;
|
||||
TestApi::setResponseDoneCallback(
|
||||
cl, [&aaFailsRemaining, &subdirBAFailsRemaining](int curlResult,
|
||||
HTTP::Request_ptr req) {
|
||||
if (req->url() == "http://localhost:2000/repo/dirA/fileAA") {
|
||||
if (aaFailsRemaining == 0)
|
||||
return false;
|
||||
|
||||
--aaFailsRemaining;
|
||||
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
|
||||
return true;
|
||||
} else if (req->url() ==
|
||||
"http://localhost:2000/repo/dirB/subdirA/.dirindex") {
|
||||
if (subdirBAFailsRemaining == 0)
|
||||
return false;
|
||||
|
||||
--subdirBAFailsRemaining;
|
||||
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
|
||||
if (repo->failure() != HTTPRepository::REPO_NO_ERROR) {
|
||||
throw sg_exception("Bad result from retry socket failure test");
|
||||
}
|
||||
|
||||
verifyFileState(p, "dirA/fileAA");
|
||||
verifyFileState(p, "dirB/subdirA/fileBAA");
|
||||
verifyFileState(p, "dirB/subdirA/fileBAC");
|
||||
|
||||
verifyRequestCount("dirA/fileAA", 3);
|
||||
verifyRequestCount("dirB/subdirA", 3);
|
||||
verifyRequestCount("dirB/subdirA/fileBAC", 1);
|
||||
}
|
||||
|
||||
void testPersistentSocketFailure(HTTP::Client *cl) {
|
||||
global_repo->clearRequestCounts();
|
||||
global_repo->clearFailFlags();
|
||||
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
SGPath p(simgear::Dir::current().path());
|
||||
p.append("http_repo_persistent_socket_fail");
|
||||
simgear::Dir pd(p);
|
||||
if (pd.exists()) {
|
||||
pd.removeChildren();
|
||||
}
|
||||
|
||||
repo.reset(new HTTPRepository(p, cl));
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
|
||||
TestApi::setResponseDoneCallback(
|
||||
cl, [](int curlResult, HTTP::Request_ptr req) {
|
||||
const auto url = req->url();
|
||||
if (url.find("http://localhost:2000/repo/dirB") == 0) {
|
||||
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
|
||||
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
|
||||
throw sg_exception("Bad result from retry socket failure test");
|
||||
}
|
||||
|
||||
verifyFileState(p, "dirA/fileAA");
|
||||
verifyRequestCount("dirA/fileAA", 1);
|
||||
|
||||
verifyRequestCount("dirD/fileDA", 1);
|
||||
verifyRequestCount("dirD/subdirDA/fileDAA", 1);
|
||||
verifyRequestCount("dirD/subdirDB/fileDBA", 1);
|
||||
}
|
||||
|
||||
void testHashOnEmptyFile()
|
||||
{
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
SGPath p(simgear::Dir::current().path());
|
||||
p.append("sgfile_compute_hash");
|
||||
simgear::Dir pd(p);
|
||||
if (pd.exists()) {
|
||||
pd.removeChildren();
|
||||
} else {
|
||||
pd.create(0700);
|
||||
}
|
||||
|
||||
SGPath fPath = p / "test_empty_file";
|
||||
|
||||
SGFile file(fPath);
|
||||
file.open(SG_IO_OUT);
|
||||
file.close();
|
||||
|
||||
const auto hash = file.computeHash();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
sglog().setLogLevels( SG_ALL, SG_INFO );
|
||||
@@ -770,6 +937,8 @@ int main(int argc, char* argv[])
|
||||
global_repo->defineFile("dirA/fileAC");
|
||||
global_repo->defineFile("dirA/subdirA/fileAAA");
|
||||
global_repo->defineFile("dirA/subdirA/fileAAB");
|
||||
global_repo->defineFile("dirA/subdirA/zeroByteFile");
|
||||
|
||||
global_repo->defineFile("dirB/subdirA/fileBAA");
|
||||
global_repo->defineFile("dirB/subdirA/fileBAB");
|
||||
global_repo->defineFile("dirB/subdirA/fileBAC");
|
||||
@@ -800,6 +969,10 @@ int main(int argc, char* argv[])
|
||||
cl.clearAllConnections();
|
||||
|
||||
testCopyInstalledChildren(&cl);
|
||||
testRetryAfterSocketFailure(&cl);
|
||||
testPersistentSocketFailure(&cl);
|
||||
|
||||
testHashOnEmptyFile();
|
||||
|
||||
std::cout << "all tests passed ok" << std::endl;
|
||||
return 0;
|
||||
|
||||
@@ -32,7 +32,7 @@ void testTarGz()
|
||||
uint8_t* buf = (uint8_t*) alloca(8192);
|
||||
size_t bufSize = f.read((char*) buf, 8192);
|
||||
|
||||
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::TarData);
|
||||
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::GZData);
|
||||
|
||||
f.close();
|
||||
}
|
||||
@@ -174,6 +174,34 @@ void testPAXAttributes()
|
||||
|
||||
}
|
||||
|
||||
void testExtractXZ()
|
||||
{
|
||||
SGPath p = SGPath(SRC_DIR);
|
||||
p.append("test.tar.xz");
|
||||
|
||||
SGBinaryFile f(p);
|
||||
f.open(SG_IO_IN);
|
||||
|
||||
SGPath extractDir = simgear::Dir::current().path() / "test_extract_xz";
|
||||
simgear::Dir pd(extractDir);
|
||||
pd.removeChildren();
|
||||
|
||||
ArchiveExtractor ex(extractDir);
|
||||
|
||||
uint8_t* buf = (uint8_t*)alloca(128);
|
||||
while (!f.eof()) {
|
||||
size_t bufSize = f.read((char*)buf, 128);
|
||||
ex.extractBytes(buf, bufSize);
|
||||
}
|
||||
|
||||
ex.flush();
|
||||
SG_VERIFY(ex.isAtEndOfArchive());
|
||||
SG_VERIFY(ex.hasError() == false);
|
||||
|
||||
SG_VERIFY((extractDir / "testDir/hello.c").exists());
|
||||
SG_VERIFY((extractDir / "testDir/foo.txt").exists());
|
||||
}
|
||||
|
||||
int main(int ac, char ** av)
|
||||
{
|
||||
testTarGz();
|
||||
@@ -181,7 +209,8 @@ int main(int ac, char ** av)
|
||||
testFilterTar();
|
||||
testExtractStreamed();
|
||||
testExtractZip();
|
||||
|
||||
testExtractXZ();
|
||||
|
||||
// disabled to avoiding checking in large PAX archive
|
||||
// testPAXAttributes();
|
||||
|
||||
|
||||
@@ -31,87 +31,18 @@
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/io/sg_file.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/package/unzip.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "ArchiveExtractor_private.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class ArchiveExtractorPrivate
|
||||
{
|
||||
public:
|
||||
ArchiveExtractorPrivate(ArchiveExtractor* o) :
|
||||
outer(o)
|
||||
{
|
||||
assert(outer);
|
||||
}
|
||||
|
||||
virtual ~ArchiveExtractorPrivate() = default;
|
||||
|
||||
typedef enum {
|
||||
INVALID = 0,
|
||||
READING_HEADER,
|
||||
READING_FILE,
|
||||
READING_PADDING,
|
||||
READING_PAX_GLOBAL_ATTRIBUTES,
|
||||
READING_PAX_FILE_ATTRIBUTES,
|
||||
PRE_END_OF_ARCHVE,
|
||||
END_OF_ARCHIVE,
|
||||
ERROR_STATE, ///< states above this are error conditions
|
||||
BAD_ARCHIVE,
|
||||
BAD_DATA,
|
||||
FILTER_STOPPED
|
||||
} State;
|
||||
|
||||
State state = INVALID;
|
||||
ArchiveExtractor* outer = nullptr;
|
||||
|
||||
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
|
||||
|
||||
virtual void flush() = 0;
|
||||
|
||||
SGPath extractRootPath()
|
||||
{
|
||||
return outer->_rootPath;
|
||||
}
|
||||
|
||||
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
|
||||
{
|
||||
return outer->filterPath(pathToExtract);
|
||||
}
|
||||
|
||||
|
||||
bool isSafePath(const std::string& p) const
|
||||
{
|
||||
if (p.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject absolute paths
|
||||
if (p.at(0) == '/') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject paths containing '..'
|
||||
size_t doubleDot = p.find("..");
|
||||
if (doubleDot != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// on POSIX could use realpath to sanity check
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
|
||||
const int ZLIB_DECODE_GZIP_HEADER = 16;
|
||||
|
||||
/* tar Header Block, from POSIX 1003.1-1990. */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -151,320 +82,413 @@ typedef struct
|
||||
#define FIFOTYPE '6' /* FIFO special */
|
||||
#define CONTTYPE '7' /* reserved */
|
||||
|
||||
const char PAX_GLOBAL_HEADER = 'g';
|
||||
const char PAX_FILE_ATTRIBUTES = 'x';
|
||||
const char PAX_GLOBAL_HEADER = 'g';
|
||||
const char PAX_FILE_ATTRIBUTES = 'x';
|
||||
|
||||
|
||||
class TarExtractorPrivate : public ArchiveExtractorPrivate
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
|
||||
const int ZLIB_DECODE_GZIP_HEADER = 16;
|
||||
|
||||
/* tar Header Block, from POSIX 1003.1-1990. */
|
||||
|
||||
|
||||
class TarExtractorPrivate : public ArchiveExtractorPrivate
|
||||
{
|
||||
public:
|
||||
union {
|
||||
UstarHeaderBlock header;
|
||||
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
|
||||
};
|
||||
|
||||
size_t bytesRemaining;
|
||||
std::unique_ptr<SGFile> currentFile;
|
||||
size_t currentFileSize;
|
||||
|
||||
uint8_t* headerPtr;
|
||||
bool skipCurrentEntry = false;
|
||||
std::string paxAttributes;
|
||||
std::string paxPathName;
|
||||
|
||||
TarExtractorPrivate(ArchiveExtractor* o) : ArchiveExtractorPrivate(o)
|
||||
{
|
||||
setState(TarExtractorPrivate::READING_HEADER);
|
||||
}
|
||||
|
||||
~TarExtractorPrivate() = default;
|
||||
|
||||
void readPaddingIfRequired()
|
||||
{
|
||||
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
|
||||
if (pad) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
|
||||
setState(READING_PADDING);
|
||||
} else {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEndOfState()
|
||||
{
|
||||
if (bytesRemaining > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->close();
|
||||
currentFile.reset();
|
||||
}
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_HEADER) {
|
||||
processHeader();
|
||||
} else if (state == PRE_END_OF_ARCHVE) {
|
||||
if (headerIsAllZeros()) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
} else {
|
||||
// what does the spec say here?
|
||||
}
|
||||
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
|
||||
parsePAXAttributes(true);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
|
||||
parsePAXAttributes(false);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PADDING) {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void setState(State newState)
|
||||
{
|
||||
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
|
||||
headerPtr = headerBytes;
|
||||
}
|
||||
|
||||
state = newState;
|
||||
}
|
||||
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
// uncompressed, just pass through directly
|
||||
processBytes((const char*)bytes, count);
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
// no-op for tar files, we process everything greedily
|
||||
}
|
||||
|
||||
void processHeader()
|
||||
{
|
||||
if (headerIsAllZeros()) {
|
||||
if (state == PRE_END_OF_ARCHVE) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
} else {
|
||||
setState(PRE_END_OF_ARCHVE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
|
||||
state = BAD_ARCHIVE;
|
||||
return;
|
||||
}
|
||||
|
||||
skipCurrentEntry = false;
|
||||
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
|
||||
if (!paxPathName.empty()) {
|
||||
tarPath = paxPathName;
|
||||
paxPathName.clear(); // clear for next file
|
||||
}
|
||||
|
||||
if (!isSafePath(tarPath)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
auto result = filterPath(tarPath);
|
||||
if (result == ArchiveExtractor::Stop) {
|
||||
setState(FILTER_STOPPED);
|
||||
return;
|
||||
} else if (result == ArchiveExtractor::Skipped) {
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
SGPath p = extractRootPath() / tarPath;
|
||||
if (header.typeflag == DIRTYPE) {
|
||||
if (!skipCurrentEntry) {
|
||||
Dir dir(p);
|
||||
dir.create(0755);
|
||||
}
|
||||
setState(READING_HEADER);
|
||||
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
if (!skipCurrentEntry) {
|
||||
currentFile.reset(new SGBinaryFile(p));
|
||||
currentFile->open(SG_IO_OUT);
|
||||
}
|
||||
setState(READING_FILE);
|
||||
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
|
||||
setState(READING_PAX_GLOBAL_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
|
||||
setState(READING_PAX_FILE_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
setState(READING_HEADER);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
|
||||
state = BAD_ARCHIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void processBytes(const char* bytes, size_t count)
|
||||
{
|
||||
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t curBytes = std::min(bytesRemaining, count);
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->write(bytes, curBytes);
|
||||
}
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
|
||||
memcpy(headerPtr, bytes, curBytes);
|
||||
bytesRemaining -= curBytes;
|
||||
headerPtr += curBytes;
|
||||
} else if (state == READING_PADDING) {
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
|
||||
bytesRemaining -= curBytes;
|
||||
paxAttributes.append(bytes, curBytes);
|
||||
}
|
||||
|
||||
checkEndOfState();
|
||||
if (count > curBytes) {
|
||||
// recurse with unprocessed bytes
|
||||
processBytes(bytes + curBytes, count - curBytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool headerIsAllZeros() const
|
||||
{
|
||||
char* headerAsChar = (char*)&header;
|
||||
for (size_t i = 0; i < offsetof(UstarHeaderBlock, magic); ++i) {
|
||||
if (*headerAsChar++ != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
|
||||
void parsePAXAttributes(bool areGlobal)
|
||||
{
|
||||
auto lineStart = 0;
|
||||
for (;;) {
|
||||
auto firstSpace = paxAttributes.find(' ', lineStart);
|
||||
auto firstEq = paxAttributes.find('=', lineStart);
|
||||
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
|
||||
uint32_t dataBytes = lengthBytes - (firstEq + 1);
|
||||
std::string name = paxAttributes.substr(firstSpace + 1, firstEq - (firstSpace + 1));
|
||||
|
||||
// dataBytes - 1 here to trim off the trailing newline
|
||||
std::string data = paxAttributes.substr(firstEq + 1, dataBytes - 1);
|
||||
|
||||
processPAXAttribute(areGlobal, name, data);
|
||||
|
||||
lineStart += lengthBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
|
||||
{
|
||||
if (!isGlobalAttr && (attrName == "path")) {
|
||||
// data is UTF-8 encoded path name
|
||||
paxPathName = data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class GZTarExtractor : public TarExtractorPrivate
|
||||
{
|
||||
public:
|
||||
|
||||
union {
|
||||
UstarHeaderBlock header;
|
||||
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
|
||||
};
|
||||
|
||||
size_t bytesRemaining;
|
||||
std::unique_ptr<SGFile> currentFile;
|
||||
size_t currentFileSize;
|
||||
z_stream zlibStream;
|
||||
uint8_t* zlibOutput;
|
||||
bool haveInitedZLib = false;
|
||||
bool uncompressedData = false; // set if reading a plain .tar (not tar.gz)
|
||||
uint8_t* headerPtr;
|
||||
bool skipCurrentEntry = false;
|
||||
std::string paxAttributes;
|
||||
std::string paxPathName;
|
||||
|
||||
TarExtractorPrivate(ArchiveExtractor* o) :
|
||||
ArchiveExtractorPrivate(o)
|
||||
GZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
|
||||
{
|
||||
memset(&zlibStream, 0, sizeof(z_stream));
|
||||
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
zlibStream.zalloc = Z_NULL;
|
||||
zlibStream.zfree = Z_NULL;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
zlibStream.next_out = zlibOutput;
|
||||
memset(&zlibStream, 0, sizeof(z_stream));
|
||||
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
zlibStream.zalloc = Z_NULL;
|
||||
zlibStream.zfree = Z_NULL;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
zlibStream.next_out = zlibOutput;
|
||||
}
|
||||
|
||||
~TarExtractorPrivate()
|
||||
~GZTarExtractor()
|
||||
{
|
||||
if (haveInitedZLib) {
|
||||
inflateEnd(&zlibStream);
|
||||
}
|
||||
free(zlibOutput);
|
||||
}
|
||||
|
||||
void readPaddingIfRequired()
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
|
||||
if (pad) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
|
||||
setState(READING_PADDING);
|
||||
} else {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEndOfState()
|
||||
{
|
||||
if (bytesRemaining > 0) {
|
||||
return;
|
||||
}
|
||||
zlibStream.next_in = (uint8_t*)bytes;
|
||||
zlibStream.avail_in = count;
|
||||
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->close();
|
||||
currentFile.reset();
|
||||
}
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_HEADER) {
|
||||
processHeader();
|
||||
} else if (state == PRE_END_OF_ARCHVE) {
|
||||
if (headerIsAllZeros()) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
if (!haveInitedZLib) {
|
||||
// now we have data, see if we're dealing with GZ-compressed data or not
|
||||
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
|
||||
// GZIP identification bytes
|
||||
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// what does the spec say here?
|
||||
// set error state
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
|
||||
parsePAXAttributes(true);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
|
||||
parsePAXAttributes(false);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PADDING) {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void setState(State newState)
|
||||
{
|
||||
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
|
||||
headerPtr = headerBytes;
|
||||
}
|
||||
haveInitedZLib = true;
|
||||
setState(TarExtractorPrivate::READING_HEADER);
|
||||
} // of init on first-bytes case
|
||||
|
||||
state = newState;
|
||||
}
|
||||
size_t writtenSize;
|
||||
// loop, running zlib() inflate and sending output bytes to
|
||||
// our request body handler. Keep calling inflate until no bytes are
|
||||
// written, and ZLIB has consumed all available input
|
||||
do {
|
||||
zlibStream.next_out = zlibOutput;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
int result = inflate(&zlibStream, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
zlibStream.next_in = (uint8_t*) bytes;
|
||||
zlibStream.avail_in = count;
|
||||
|
||||
if (!haveInitedZLib) {
|
||||
// now we have data, see if we're dealing with GZ-compressed data or not
|
||||
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
|
||||
// GZIP identification bytes
|
||||
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
UstarHeaderBlock* header = (UstarHeaderBlock*)bytes;
|
||||
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "didn't find tar magic in header");
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
|
||||
uncompressedData = true;
|
||||
}
|
||||
|
||||
haveInitedZLib = true;
|
||||
setState(TarExtractorPrivate::READING_HEADER);
|
||||
} // of init on first-bytes case
|
||||
|
||||
if (uncompressedData) {
|
||||
processBytes((const char*) bytes, count);
|
||||
} else {
|
||||
size_t writtenSize;
|
||||
// loop, running zlib() inflate and sending output bytes to
|
||||
// our request body handler. Keep calling inflate until no bytes are
|
||||
// written, and ZLIB has consumed all available input
|
||||
do {
|
||||
zlibStream.next_out = zlibOutput;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
int result = inflate(&zlibStream, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
|
||||
}
|
||||
else if (result == Z_BUF_ERROR) {
|
||||
// transient error, fall through
|
||||
}
|
||||
else {
|
||||
// _error = result;
|
||||
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
|
||||
if (writtenSize > 0) {
|
||||
processBytes((const char*) zlibOutput, writtenSize);
|
||||
}
|
||||
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
|
||||
} // of Zlib-compressed data
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
// no-op for tar files, we process everything greedily
|
||||
}
|
||||
|
||||
void processHeader()
|
||||
{
|
||||
if (headerIsAllZeros()) {
|
||||
if (state == PRE_END_OF_ARCHVE) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
} else if (result == Z_BUF_ERROR) {
|
||||
// transient error, fall through
|
||||
} else {
|
||||
setState(PRE_END_OF_ARCHVE);
|
||||
// _error = result;
|
||||
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
|
||||
state = BAD_ARCHIVE;
|
||||
return;
|
||||
}
|
||||
|
||||
skipCurrentEntry = false;
|
||||
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
|
||||
if (!paxPathName.empty()) {
|
||||
tarPath = paxPathName;
|
||||
paxPathName.clear(); // clear for next file
|
||||
}
|
||||
|
||||
if (!isSafePath(tarPath)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
auto result = filterPath(tarPath);
|
||||
if (result == ArchiveExtractor::Stop) {
|
||||
setState(FILTER_STOPPED);
|
||||
return;
|
||||
} else if (result == ArchiveExtractor::Skipped) {
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
SGPath p = extractRootPath() / tarPath;
|
||||
if (header.typeflag == DIRTYPE) {
|
||||
if (!skipCurrentEntry) {
|
||||
Dir dir(p);
|
||||
dir.create(0755);
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
|
||||
if (writtenSize > 0) {
|
||||
processBytes((const char*)zlibOutput, writtenSize);
|
||||
}
|
||||
setState(READING_HEADER);
|
||||
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
if (!skipCurrentEntry) {
|
||||
currentFile.reset(new SGBinaryFile(p));
|
||||
currentFile->open(SG_IO_OUT);
|
||||
}
|
||||
setState(READING_FILE);
|
||||
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
|
||||
setState(READING_PAX_GLOBAL_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
|
||||
setState(READING_PAX_FILE_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
setState(READING_HEADER);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
|
||||
state = BAD_ARCHIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void processBytes(const char* bytes, size_t count)
|
||||
{
|
||||
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t curBytes = std::min(bytesRemaining, count);
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->write(bytes, curBytes);
|
||||
}
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
|
||||
memcpy(headerPtr, bytes, curBytes);
|
||||
bytesRemaining -= curBytes;
|
||||
headerPtr += curBytes;
|
||||
} else if (state == READING_PADDING) {
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
|
||||
bytesRemaining -= curBytes;
|
||||
paxAttributes.append(bytes, curBytes);
|
||||
}
|
||||
|
||||
checkEndOfState();
|
||||
if (count > curBytes) {
|
||||
// recurse with unprocessed bytes
|
||||
processBytes(bytes + curBytes, count - curBytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool headerIsAllZeros() const
|
||||
{
|
||||
char* headerAsChar = (char*) &header;
|
||||
for (size_t i=0; i < offsetof(UstarHeaderBlock, magic); ++i) {
|
||||
if (*headerAsChar++ != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
|
||||
void parsePAXAttributes(bool areGlobal)
|
||||
{
|
||||
auto lineStart = 0;
|
||||
for (;;) {
|
||||
auto firstSpace = paxAttributes.find(' ', lineStart);
|
||||
auto firstEq = paxAttributes.find('=', lineStart);
|
||||
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
|
||||
uint32_t dataBytes = lengthBytes - (firstEq + 1);
|
||||
std::string name = paxAttributes.substr(firstSpace+1, firstEq - (firstSpace + 1));
|
||||
|
||||
// dataBytes - 1 here to trim off the trailing newline
|
||||
std::string data = paxAttributes.substr(firstEq+1, dataBytes - 1);
|
||||
|
||||
processPAXAttribute(areGlobal, name, data);
|
||||
|
||||
lineStart += lengthBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
|
||||
{
|
||||
if (!isGlobalAttr && (attrName == "path")) {
|
||||
// data is UTF-8 encoded path name
|
||||
paxPathName = data;
|
||||
}
|
||||
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
|
||||
}
|
||||
|
||||
private:
|
||||
z_stream zlibStream;
|
||||
uint8_t* zlibOutput;
|
||||
bool haveInitedZLib = false;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if 1 || defined(HAVE_XZ)
|
||||
|
||||
#include <lzma.h>
|
||||
|
||||
class XZTarExtractor : public TarExtractorPrivate
|
||||
{
|
||||
public:
|
||||
XZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
|
||||
{
|
||||
_xzStream = LZMA_STREAM_INIT;
|
||||
_outputBuffer = (uint8_t*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
|
||||
|
||||
auto ret = lzma_stream_decoder(&_xzStream, UINT64_MAX, LZMA_TELL_ANY_CHECK);
|
||||
if (ret != LZMA_OK) {
|
||||
setState(BAD_ARCHIVE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~XZTarExtractor()
|
||||
{
|
||||
free(_outputBuffer);
|
||||
}
|
||||
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
lzma_action action = LZMA_RUN;
|
||||
_xzStream.next_in = bytes;
|
||||
_xzStream.avail_in = count;
|
||||
|
||||
size_t writtenSize;
|
||||
do {
|
||||
_xzStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
_xzStream.next_out = _outputBuffer;
|
||||
|
||||
const auto ret = lzma_code(&_xzStream, action);
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _xzStream.avail_out;
|
||||
if (writtenSize > 0) {
|
||||
processBytes((const char*)_outputBuffer, writtenSize);
|
||||
}
|
||||
|
||||
if (ret == LZMA_GET_CHECK) {
|
||||
//
|
||||
} else if (ret == LZMA_STREAM_END) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
break;
|
||||
} else if (ret != LZMA_OK) {
|
||||
setState(BAD_ARCHIVE);
|
||||
break;
|
||||
}
|
||||
} while ((_xzStream.avail_in > 0) || (writtenSize > 0));
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
const auto ret = lzma_code(&_xzStream, LZMA_FINISH);
|
||||
if (ret != LZMA_STREAM_END) {
|
||||
setState(BAD_ARCHIVE);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
lzma_stream _xzStream;
|
||||
uint8_t* _outputBuffer = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
extern "C" {
|
||||
void fill_memory_filefunc(zlib_filefunc_def*);
|
||||
}
|
||||
@@ -509,32 +533,34 @@ public:
|
||||
const size_t BUFFER_SIZE = 1024 * 1024;
|
||||
void* buf = malloc(BUFFER_SIZE);
|
||||
|
||||
try {
|
||||
int result = unzGoToFirstFile(zip);
|
||||
if (result != UNZ_OK) {
|
||||
throw sg_exception("failed to go to first file in archive");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
extractCurrentFile(zip, (char*)buf, BUFFER_SIZE);
|
||||
if (state == FILTER_STOPPED) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = unzGoToNextFile(zip);
|
||||
if (result == UNZ_END_OF_LIST_OF_FILE) {
|
||||
break;
|
||||
}
|
||||
else if (result != UNZ_OK) {
|
||||
throw sg_io_exception("failed to go to next file in the archive");
|
||||
}
|
||||
}
|
||||
state = END_OF_ARCHIVE;
|
||||
}
|
||||
catch (sg_exception&) {
|
||||
int result = unzGoToFirstFile(zip);
|
||||
if (result != UNZ_OK) {
|
||||
SG_LOG(SG_IO, SG_ALERT, outer->rootPath() << "failed to go to first file in archive:" << result);
|
||||
state = BAD_ARCHIVE;
|
||||
free(buf);
|
||||
unzClose(zip);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
extractCurrentFile(zip, (char*)buf, BUFFER_SIZE);
|
||||
if (state == FILTER_STOPPED) {
|
||||
state = END_OF_ARCHIVE;
|
||||
break;
|
||||
}
|
||||
|
||||
result = unzGoToNextFile(zip);
|
||||
if (result == UNZ_END_OF_LIST_OF_FILE) {
|
||||
state = END_OF_ARCHIVE;
|
||||
break;
|
||||
} else if (result != UNZ_OK) {
|
||||
SG_LOG(SG_IO, SG_ALERT, outer->rootPath() << "failed to go to next file in archive:" << result);
|
||||
state = BAD_ARCHIVE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(buf);
|
||||
unzClose(zip);
|
||||
}
|
||||
@@ -592,7 +618,7 @@ public:
|
||||
|
||||
outFile.open(path, std::ios::binary | std::ios::trunc | std::ios::out);
|
||||
if (outFile.fail()) {
|
||||
throw sg_io_exception("failed to open output file for writing", path);
|
||||
throw sg_io_exception("failed to open output file for writing:" + strutils::error_string(errno), path);
|
||||
}
|
||||
|
||||
while (!eof) {
|
||||
@@ -633,17 +659,19 @@ void ArchiveExtractor::extractBytes(const uint8_t* bytes, size_t count)
|
||||
|
||||
if (r == TarData) {
|
||||
d.reset(new TarExtractorPrivate(this));
|
||||
}
|
||||
else if (r == ZipData) {
|
||||
d.reset(new ZipExtractorPrivate(this));
|
||||
}
|
||||
else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
|
||||
} else if (r == GZData) {
|
||||
d.reset(new GZTarExtractor(this));
|
||||
} else if (r == XZData) {
|
||||
d.reset(new XZTarExtractor(this));
|
||||
} else if (r == ZipData) {
|
||||
d.reset(new ZipExtractorPrivate(this));
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
|
||||
_invalidDataType = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if hit here, we created the extractor. Feed the prefbuffer
|
||||
// if hit here, we created the extractor. Feed the prefbuffer
|
||||
// bytes through it
|
||||
d->extractBytes((uint8_t*) _prebuffer.data(), _prebuffer.size());
|
||||
_prebuffer.clear();
|
||||
@@ -695,9 +723,18 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::determineType(const uint8_t*
|
||||
return ZipData;
|
||||
}
|
||||
|
||||
auto r = isTarData(bytes, count);
|
||||
if ((r == TarData) || (r == InsufficientData))
|
||||
return r;
|
||||
if (count < 6) {
|
||||
return InsufficientData;
|
||||
}
|
||||
|
||||
const uint8_t XZ_HEADER[6] = {0xFD, '7', 'z', 'X', 'Z', 0x00};
|
||||
if (memcmp(bytes, XZ_HEADER, 6) == 0) {
|
||||
return XZData;
|
||||
}
|
||||
|
||||
auto r = isTarData(bytes, count);
|
||||
if ((r == TarData) || (r == InsufficientData) || (r == GZData))
|
||||
return r;
|
||||
|
||||
return Invalid;
|
||||
}
|
||||
@@ -710,6 +747,8 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
}
|
||||
|
||||
UstarHeaderBlock* header = 0;
|
||||
DetermineResult result = InsufficientData;
|
||||
|
||||
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
|
||||
// GZIP identification bytes
|
||||
z_stream z;
|
||||
@@ -727,11 +766,11 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
int result = inflate(&z, Z_SYNC_FLUSH);
|
||||
if ((result == Z_OK) || (result == Z_STREAM_END)) {
|
||||
int zResult = inflate(&z, Z_SYNC_FLUSH);
|
||||
if ((zResult == Z_OK) || (zResult == Z_STREAM_END)) {
|
||||
// all good
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << result);
|
||||
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << zResult);
|
||||
inflateEnd(&z);
|
||||
return Invalid; // not tar data
|
||||
}
|
||||
@@ -744,6 +783,7 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
|
||||
header = reinterpret_cast<UstarHeaderBlock*>(zlibOutput);
|
||||
inflateEnd(&z);
|
||||
result = GZData;
|
||||
} else {
|
||||
// uncompressed tar
|
||||
if (count < TAR_HEADER_BLOCK_SIZE) {
|
||||
@@ -751,13 +791,14 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
}
|
||||
|
||||
header = (UstarHeaderBlock*) bytes;
|
||||
result = TarData;
|
||||
}
|
||||
|
||||
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
return TarData;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ArchiveExtractor::extractLocalFile(const SGPath& archiveFile)
|
||||
|
||||
@@ -36,15 +36,16 @@ public:
|
||||
ArchiveExtractor(const SGPath& rootPath);
|
||||
virtual ~ArchiveExtractor();
|
||||
|
||||
enum DetermineResult
|
||||
{
|
||||
Invalid,
|
||||
InsufficientData,
|
||||
TarData,
|
||||
ZipData
|
||||
};
|
||||
enum DetermineResult {
|
||||
Invalid,
|
||||
InsufficientData,
|
||||
TarData,
|
||||
ZipData,
|
||||
GZData, // Gzipped-tar
|
||||
XZData // XZ (aka LZMA) tar
|
||||
};
|
||||
|
||||
static DetermineResult determineType(const uint8_t* bytes, size_t count);
|
||||
static DetermineResult determineType(const uint8_t* bytes, size_t count);
|
||||
|
||||
/**
|
||||
* @brief API to extract a local zip or tar.gz
|
||||
@@ -70,6 +71,11 @@ public:
|
||||
Stop
|
||||
};
|
||||
|
||||
SGPath rootPath() const
|
||||
{
|
||||
return _rootPath;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ public:
|
||||
/// Default constructor, initializes the instance to lat = lon = elev = 0
|
||||
SGGeod(void);
|
||||
|
||||
/**
|
||||
return an SGGeod for which isValid() returns false.
|
||||
This is necessaerby becuase for historical reasons, ther defaulrt constructor above initialsies to zero,zero,zero
|
||||
which *is*
|
||||
*/
|
||||
static SGGeod invalid();
|
||||
|
||||
/// Factory from angular values in radians and elevation is 0
|
||||
static SGGeod fromRad(double lon, double lat);
|
||||
/// Factory from angular values in degrees and elevation is 0
|
||||
|
||||
@@ -663,3 +663,8 @@ SGGeodesy::radialIntersection(const SGGeod& a, double aRadial,
|
||||
result = SGGeod::fromGeoc(r);
|
||||
return true;
|
||||
}
|
||||
|
||||
SGGeod SGGeod::invalid()
|
||||
{
|
||||
return SGGeod::fromDeg(-999.9, -999.0);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ ResourceManager* ResourceManager::instance()
|
||||
return static_manager;
|
||||
}
|
||||
|
||||
bool ResourceManager::haveInstance()
|
||||
{
|
||||
return static_manager != nullptr;
|
||||
}
|
||||
|
||||
ResourceManager::~ResourceManager()
|
||||
{
|
||||
assert(this == static_manager);
|
||||
@@ -56,6 +61,15 @@ ResourceManager::~ResourceManager()
|
||||
std::for_each(_providers.begin(), _providers.end(),
|
||||
[](ResourceProvider* p) { delete p; });
|
||||
}
|
||||
|
||||
void ResourceManager::reset()
|
||||
{
|
||||
if (static_manager) {
|
||||
delete static_manager;
|
||||
static_manager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* trivial provider using a fixed base path
|
||||
*/
|
||||
@@ -107,6 +121,8 @@ void ResourceManager::removeProvider(ResourceProvider* aProvider)
|
||||
SG_LOG(SG_GENERAL, SG_DEV_ALERT, "unknown provider doing remove");
|
||||
return;
|
||||
}
|
||||
|
||||
_providers.erase(it);
|
||||
}
|
||||
|
||||
SGPath ResourceManager::findPath(const std::string& aResource, SGPath aContext)
|
||||
|
||||
@@ -45,8 +45,12 @@ public:
|
||||
PRIORITY_HIGH = 1000
|
||||
} Priority;
|
||||
|
||||
static ResourceManager* instance();
|
||||
|
||||
static ResourceManager* instance();
|
||||
|
||||
static bool haveInstance();
|
||||
|
||||
static void reset();
|
||||
|
||||
/**
|
||||
* add a simple fixed resource location, to resolve against
|
||||
*/
|
||||
|
||||
@@ -44,9 +44,11 @@
|
||||
#if defined(SG_WINDOWS)
|
||||
# include <direct.h>
|
||||
# include <sys/utime.h>
|
||||
# include <Shlwapi.h>
|
||||
#endif
|
||||
|
||||
#include "sg_path.hxx"
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
|
||||
using std::string;
|
||||
using simgear::strutils::starts_with;
|
||||
@@ -194,7 +196,8 @@ SGPath::SGPath(PermissionChecker validator)
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(true)
|
||||
_cacheEnabled(true),
|
||||
_existsCached(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -205,7 +208,8 @@ SGPath::SGPath( const std::string& p, PermissionChecker validator )
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(true)
|
||||
_cacheEnabled(true),
|
||||
_existsCached(false)
|
||||
{
|
||||
fix();
|
||||
}
|
||||
@@ -230,7 +234,8 @@ SGPath::SGPath( const SGPath& p,
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(p._cacheEnabled)
|
||||
_cacheEnabled(p._cacheEnabled),
|
||||
_existsCached(false)
|
||||
{
|
||||
append(r);
|
||||
fix();
|
||||
@@ -510,6 +515,19 @@ void SGPath::checkAccess() const
|
||||
|
||||
bool SGPath::exists() const
|
||||
{
|
||||
#if defined(SG_WINDOWS)
|
||||
// optimisation: _wstat is slow, eg for TerraSync
|
||||
if (!_cached && !_existsCached) {
|
||||
std::wstring w(wstr());
|
||||
if ((path.length() > 1) && (path.back() == '/')) {
|
||||
w.pop_back();
|
||||
}
|
||||
|
||||
_existsCached = true;
|
||||
_exists = PathFileExistsW(w.c_str());
|
||||
return _exists;
|
||||
}
|
||||
#endif
|
||||
validate();
|
||||
return _exists;
|
||||
}
|
||||
@@ -978,8 +996,9 @@ SGPath SGPath::realpath() const
|
||||
// (needed for fgValidatePath security)
|
||||
{
|
||||
if (path.empty()) {
|
||||
return SGPath(".").realpath(); // current directory
|
||||
return simgear::Dir::current().path();
|
||||
}
|
||||
|
||||
std::string this_dir = dir();
|
||||
if (isAbsolute() && this_dir.empty()) { // top level
|
||||
this_dir = "/";
|
||||
|
||||
@@ -356,6 +356,7 @@ private:
|
||||
mutable bool _exists : 1;
|
||||
mutable bool _isDir : 1;
|
||||
mutable bool _isFile : 1;
|
||||
mutable bool _existsCached : 1; ///< only used on Windows
|
||||
mutable time_t _modTime;
|
||||
mutable size_t _size;
|
||||
};
|
||||
|
||||
@@ -267,6 +267,38 @@ namespace simgear {
|
||||
return do_strip( s, BOTHSTRIP );
|
||||
}
|
||||
|
||||
string makeStringSafeForPropertyName(const std::string& str)
|
||||
{
|
||||
// This function replaces all characters in 'str' that are not
|
||||
// alphanumeric or '-'.
|
||||
// TODO: make the function multibyte safe.
|
||||
string res = str;
|
||||
|
||||
int index = 0;
|
||||
for (char& c : res) {
|
||||
if (!std::isalpha(c) && !std::isdigit(c) && c != '-') {
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '_':
|
||||
case '.':
|
||||
case '/':
|
||||
case '\\':
|
||||
res[index] = '-';
|
||||
break;
|
||||
default:
|
||||
res[index] = '_';
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "makeStringSafeForPropertyName: Modified '" << str << "' to '" << res << "'");
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
stripTrailingNewlines_inplace(string& s)
|
||||
{
|
||||
|
||||
@@ -75,7 +75,9 @@ namespace simgear {
|
||||
std::string rstrip( const std::string& s );
|
||||
std::string strip( const std::string& s );
|
||||
|
||||
/**
|
||||
std::string makeStringSafeForPropertyName(const std::string& str);
|
||||
|
||||
/**
|
||||
* Return a new string with any trailing \\r and \\n characters removed.
|
||||
* Typically useful to clean a CR-terminated line obtained from
|
||||
* std::getline() which, upon reading CRLF (\\r\\n), discards the Line
|
||||
|
||||
@@ -737,6 +737,11 @@ void testDecodeHex()
|
||||
SG_VERIFY(decoded == data1);
|
||||
}
|
||||
|
||||
void test_makeStringSafeForPropertyName()
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::makeStringSafeForPropertyName(" ABC/01234\t:\\\"_*$"), "-ABC-01234-_-_-__");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
test_strip();
|
||||
@@ -761,6 +766,7 @@ int main(int argc, char* argv[])
|
||||
test_formatGeod();
|
||||
test_iequals();
|
||||
testDecodeHex();
|
||||
|
||||
test_makeStringSafeForPropertyName();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ void naRuntimeError(naContext c, const char* fmt, ...)
|
||||
|
||||
void naRethrowError(naContext subc)
|
||||
{
|
||||
strncpy(subc->callParent->error, subc->error, sizeof(subc->error));
|
||||
strncpy(subc->callParent->error, subc->error, sizeof(subc->callParent->error));
|
||||
subc->callParent->dieArg = subc->dieArg;
|
||||
longjmp(subc->callParent->jumpHandle, 1);
|
||||
}
|
||||
@@ -248,7 +248,7 @@ void naFreeContext(naContext c)
|
||||
// than I have right now. So instead I'm clearing the stack tops here, so
|
||||
// a freed context looks the same as a new one returned by initContext.
|
||||
|
||||
c->fTop = c->opTop = c->markTop = 0;
|
||||
c->fTop = c->opTop = c->markTop = c->ntemps = 0;
|
||||
|
||||
c->nextFree = globals->freeContexts;
|
||||
globals->freeContexts = c;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "NasalString.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept> // for std::runtime_error
|
||||
|
||||
namespace nasal
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace nasal
|
||||
|
||||
class NasalMainLoopRecipient : public simgear::Emesary::IReceiver {
|
||||
public:
|
||||
NasalMainLoopRecipient() : receiveCount(0) {
|
||||
NasalMainLoopRecipient() : receiveCount(0), Active(false), CanWait(false) {
|
||||
simgear::Emesary::GlobalTransmitter::instance()->Register(*this);
|
||||
}
|
||||
virtual ~NasalMainLoopRecipient() {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "iolib.h"
|
||||
|
||||
static void ghostDestroy(void* g);
|
||||
naGhostType naIOGhostType = { ghostDestroy, "iofile" };
|
||||
naGhostType naIOGhostType = { ghostDestroy, "iofile", NULL, NULL };
|
||||
|
||||
static struct naIOGhost* ioghost(naRef r)
|
||||
{
|
||||
|
||||
@@ -472,7 +472,7 @@ static naRef f_sprintf(naContext c, naRef me, int argc, naRef* args)
|
||||
} else {
|
||||
arg = naNumValue(arg);
|
||||
if(naIsNil(arg))
|
||||
fout = dosprintf(fstr, "nil");
|
||||
fout = dosprintf("nil");
|
||||
else if(t=='d' || t=='i' || t=='c')
|
||||
fout = dosprintf(fstr, (int)naNumValue(arg).num);
|
||||
else if(t=='o' || t=='u' || t=='x' || t=='X')
|
||||
|
||||
@@ -100,7 +100,14 @@ naRef naNewHash(struct Context* c)
|
||||
|
||||
naRef naNewCode(struct Context* c)
|
||||
{
|
||||
return naNew(c, T_CODE);
|
||||
naRef r = naNew(c, T_CODE);
|
||||
// naNew can return a previously used naCode. naCodeGen will init
|
||||
// all these members but a GC can occur inside naCodeGen, so we see
|
||||
// partially initalized state here. To avoid this, clear out the values
|
||||
// which mark() cares about.
|
||||
PTR(r).code->srcFile = naNil();
|
||||
PTR(r).code->nConstants = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
naRef naNewCCode(struct Context* c, naCFunction fptr)
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include "code.h"
|
||||
|
||||
static void lockDestroy(void* lock) { naFreeLock(lock); }
|
||||
static naGhostType LockType = { lockDestroy };
|
||||
static naGhostType LockType = { lockDestroy, NULL, NULL, NULL };
|
||||
|
||||
static void semDestroy(void* sem) { naFreeSem(sem); }
|
||||
static naGhostType SemType = { semDestroy };
|
||||
static naGhostType SemType = { semDestroy, NULL, NULL, NULL };
|
||||
|
||||
typedef struct {
|
||||
naContext ctx;
|
||||
|
||||
@@ -143,9 +143,15 @@ protected:
|
||||
Dir d(m_owner->installRoot());
|
||||
SGPath p = d.file("catalog.xml");
|
||||
sg_ofstream f(p, std::ios::out | std::ios::trunc);
|
||||
f.write(m_buffer.data(), m_buffer.size());
|
||||
const auto sz = m_buffer.size();
|
||||
f.write(m_buffer.data(), sz);
|
||||
f.close();
|
||||
|
||||
if (f.fail()) {
|
||||
m_owner->refreshComplete(Delegate::FAIL_FILESYSTEM);
|
||||
return;
|
||||
}
|
||||
|
||||
time(&m_owner->m_retrievedTime);
|
||||
m_owner->writeTimestamp();
|
||||
m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
|
||||
@@ -595,13 +601,17 @@ void Catalog::setUserEnabled(bool b)
|
||||
|
||||
m_userEnabled = b;
|
||||
SGPath disableMarkerFile = installRoot() / "_disabled_";
|
||||
if (m_userEnabled) {
|
||||
sg_ofstream of(disableMarkerFile);
|
||||
of << "1\n"; // touch the file
|
||||
|
||||
if (m_userEnabled == false) {
|
||||
sg_ofstream of(disableMarkerFile, std::ios::trunc | std::ios::out);
|
||||
of << "1" << std::flush; // touch the file
|
||||
of.close();
|
||||
} else {
|
||||
bool ok = disableMarkerFile.remove();
|
||||
if (!ok) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Failed to remove catalog-disable marker file:" << disableMarkerFile);
|
||||
if (disableMarkerFile.exists()) {
|
||||
const bool ok = disableMarkerFile.remove();
|
||||
if (!ok) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Failed to remove catalog-disable marker file:" << disableMarkerFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,6 +625,8 @@ void Catalog::setUserEnabled(bool b)
|
||||
|
||||
void Catalog::processAlternate(SGPropertyNode_ptr alt)
|
||||
{
|
||||
m_refreshRequest.reset();
|
||||
|
||||
std::string altId;
|
||||
const auto idPtr = alt->getStringValue("id");
|
||||
if (idPtr) {
|
||||
@@ -639,24 +651,81 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
|
||||
return;
|
||||
}
|
||||
|
||||
// we have an alternate ID, and it's differnt from our ID, so let's
|
||||
// we have an alternate ID, and it's different from our ID, so let's
|
||||
// define a new catalog
|
||||
if (!altId.empty()) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "Adding new catalog:" << altId << " as version alternate for " << id());
|
||||
// new catalog being added
|
||||
createFromUrl(root(), altUrl);
|
||||
|
||||
// and we can go idle now
|
||||
// don't auto-re-add Catalogs the user has explicilty rmeoved, that would
|
||||
// suck
|
||||
const auto removedByUser = root()->explicitlyRemovedCatalogs();
|
||||
auto it = std::find(removedByUser.begin(), removedByUser.end(), altId);
|
||||
if (it != removedByUser.end()) {
|
||||
changeStatus(Delegate::FAIL_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_WARN,
|
||||
"Adding new catalog:" << altId << " as version alternate for "
|
||||
<< id());
|
||||
// new catalog being added
|
||||
auto newCat = createFromUrl(root(), altUrl);
|
||||
|
||||
bool didRun = false;
|
||||
newCat->m_migratedFrom = this;
|
||||
|
||||
auto migratePackagesCb = [didRun](Catalog *c) mutable {
|
||||
// removing callbacks is awkward, so use this
|
||||
// flag to only run once. (and hence, we need to be mutable)
|
||||
if (didRun)
|
||||
return;
|
||||
|
||||
if (c->status() == Delegate::STATUS_REFRESHED) {
|
||||
didRun = true;
|
||||
|
||||
string_list existing;
|
||||
for (const auto &pack : c->migratedFrom()->installedPackages()) {
|
||||
existing.push_back(pack->id());
|
||||
}
|
||||
|
||||
const int count = c->markPackagesForInstallation(existing);
|
||||
SG_LOG(
|
||||
SG_GENERAL, SG_INFO,
|
||||
"Marked " << count
|
||||
<< " packages from previous catalog for installation");
|
||||
}
|
||||
};
|
||||
|
||||
newCat->addStatusCallback(migratePackagesCb);
|
||||
// and we can go idle now
|
||||
changeStatus(Delegate::FAIL_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "Migrating catalog " << id() << " to new URL:" << altUrl);
|
||||
setUrl(altUrl);
|
||||
Downloader* dl = new Downloader(this, altUrl);
|
||||
root()->makeHTTPRequest(dl);
|
||||
m_refreshRequest = new Downloader(this, altUrl);
|
||||
root()->makeHTTPRequest(m_refreshRequest);
|
||||
}
|
||||
|
||||
int Catalog::markPackagesForInstallation(const string_list &packageIds) {
|
||||
int result = 0;
|
||||
|
||||
for (const auto &id : packageIds) {
|
||||
auto ourPkg = getPackageById(id);
|
||||
if (!ourPkg)
|
||||
continue;
|
||||
|
||||
auto existing = ourPkg->existingInstall();
|
||||
if (!existing) {
|
||||
ourPkg->markForInstall();
|
||||
++result;
|
||||
}
|
||||
} // of outer package ID candidates iteration
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CatalogRef Catalog::migratedFrom() const { return m_migratedFrom; }
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <map>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
@@ -93,7 +94,7 @@ public:
|
||||
|
||||
/**
|
||||
* retrieve all the packages in the catalog which are installed
|
||||
* and have a pendig update
|
||||
* and have a pending update
|
||||
*/
|
||||
PackageList packagesNeedingUpdate() const;
|
||||
|
||||
@@ -151,7 +152,32 @@ public:
|
||||
|
||||
bool isUserEnabled() const;
|
||||
void setUserEnabled(bool b);
|
||||
private:
|
||||
|
||||
/**
|
||||
* Given a list of package IDs, mark all which exist in this package,
|
||||
* for installation. ANy packahe IDs not present in this catalog,
|
||||
* will be ignored.
|
||||
*
|
||||
* @result The number for packages newly marked for installation.
|
||||
*/
|
||||
int markPackagesForInstallation(const string_list &packageIds);
|
||||
|
||||
/**
|
||||
* When a catalog is added due to migration, this will contain the
|
||||
* Catalog which triggered the add. Usually this will be a catalog
|
||||
* corresponding to an earlier version.
|
||||
*
|
||||
* Note it's only valid at the time, the migration actually took place;
|
||||
* when the new catalog is loaded from disk, this value will return
|
||||
* null.
|
||||
*
|
||||
* This is intended to allow Uis to show a 'catalog was migrated'
|
||||
* feedback, when they see a catalog refresh, which has a non-null
|
||||
* value of this method.
|
||||
*/
|
||||
CatalogRef migratedFrom() const;
|
||||
|
||||
private:
|
||||
Catalog(Root* aRoot);
|
||||
|
||||
class Downloader;
|
||||
@@ -197,6 +223,8 @@ private:
|
||||
PackageWeakMap m_variantDict;
|
||||
|
||||
function_list<Callback> m_statusCallbacks;
|
||||
|
||||
CatalogRef m_migratedFrom;
|
||||
};
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
@@ -166,7 +166,8 @@ int parseTest()
|
||||
{
|
||||
SGPath rootPath = simgear::Dir::current().path();
|
||||
rootPath.append("testRoot");
|
||||
pkg::Root* root = new pkg::Root(rootPath, "8.1.12");
|
||||
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setLocale("de");
|
||||
pkg::CatalogRef cat = pkg::Catalog::createFromPath(root, SGPath(SRC_DIR "/catalogTest1"));
|
||||
|
||||
@@ -344,7 +345,6 @@ int parseTest()
|
||||
SG_CHECK_EQUAL(urls.size(), 3);
|
||||
SG_CHECK_EQUAL(urls.at(1), "http://localhost:2000/mirrorB/b737.tar.gz");
|
||||
|
||||
delete root;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -827,7 +827,10 @@ void testVersionMigrateToId(HTTP::Client* cl)
|
||||
|
||||
it = std::find(enabledCats.begin(), enabledCats.end(), altCat);
|
||||
SG_VERIFY(it != enabledCats.end());
|
||||
|
||||
|
||||
SG_CHECK_EQUAL(altCat->packagesNeedingUpdate().size(),
|
||||
1); // should be the 737
|
||||
|
||||
// install a parallel package from the new catalog
|
||||
pkg::PackageRef p2 = root->getPackageById("org.flightgear.test.catalog-alt.b737-NG");
|
||||
SG_CHECK_EQUAL(p2->id(), "b737-NG");
|
||||
@@ -840,8 +843,8 @@ void testVersionMigrateToId(HTTP::Client* cl)
|
||||
pkg::PackageRef p3 = root->getPackageById("b737-NG");
|
||||
SG_CHECK_EQUAL(p2, p3);
|
||||
}
|
||||
|
||||
// test that re-init-ing doesn't mirgate again
|
||||
|
||||
// test that re-init-ing doesn't migrate again
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
|
||||
root->setHTTPClient(cl);
|
||||
@@ -950,7 +953,7 @@ int parseInvalidTest()
|
||||
{
|
||||
SGPath rootPath = simgear::Dir::current().path();
|
||||
rootPath.append("testRoot");
|
||||
pkg::Root* root = new pkg::Root(rootPath, "8.1.12");
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.12"));
|
||||
pkg::CatalogRef cat = pkg::Catalog::createFromPath(root, SGPath(SRC_DIR "/catalogTestInvalid"));
|
||||
SG_VERIFY(cat.valid());
|
||||
|
||||
@@ -1184,6 +1187,128 @@ void testMirrorsFailure(HTTP::Client* cl)
|
||||
|
||||
}
|
||||
|
||||
void testMigrateInstalled(HTTP::Client *cl) {
|
||||
SGPath rootPath(simgear::Dir::current().path());
|
||||
rootPath.append("pkg_migrate_installed");
|
||||
simgear::Dir pd(rootPath);
|
||||
pd.removeChildren();
|
||||
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
pkg::CatalogRef oldCatalog, newCatalog;
|
||||
|
||||
{
|
||||
oldCatalog = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
pkg::PackageRef p1 =
|
||||
root->getPackageById("org.flightgear.test.catalog1.b747-400");
|
||||
p1->install();
|
||||
auto p2 = root->getPackageById("org.flightgear.test.catalog1.c172p");
|
||||
p2->install();
|
||||
auto p3 = root->getPackageById("org.flightgear.test.catalog1.b737-NG");
|
||||
p3->install();
|
||||
waitForUpdateComplete(cl, root);
|
||||
}
|
||||
|
||||
{
|
||||
newCatalog = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest2/catalog.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
string_list existing;
|
||||
for (const auto &pack : oldCatalog->installedPackages()) {
|
||||
existing.push_back(pack->id());
|
||||
}
|
||||
|
||||
SG_CHECK_EQUAL(4, existing.size());
|
||||
|
||||
int result = newCatalog->markPackagesForInstallation(existing);
|
||||
SG_CHECK_EQUAL(2, result);
|
||||
SG_CHECK_EQUAL(2, newCatalog->packagesNeedingUpdate().size());
|
||||
|
||||
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
|
||||
auto ins = p1->existingInstall();
|
||||
SG_CHECK_EQUAL(0, ins->revsion());
|
||||
}
|
||||
|
||||
{
|
||||
root->scheduleAllUpdates();
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
SG_CHECK_EQUAL(0, newCatalog->packagesNeedingUpdate().size());
|
||||
|
||||
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
|
||||
auto ins = p1->existingInstall();
|
||||
SG_CHECK_EQUAL(ins->revsion(), p1->revision());
|
||||
}
|
||||
}
|
||||
|
||||
void testDontMigrateRemoved(HTTP::Client *cl) {
|
||||
global_catalogVersion = 2; // version which has migration info
|
||||
SGPath rootPath(simgear::Dir::current().path());
|
||||
rootPath.append("cat_dont_migrate_id");
|
||||
simgear::Dir pd(rootPath);
|
||||
pd.removeChildren();
|
||||
|
||||
// install and mnaully remove the alt catalog
|
||||
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest1/catalog-alt.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
root->removeCatalogById("org.flightgear.test.catalog-alt");
|
||||
}
|
||||
|
||||
// install the migration catalog
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
SG_VERIFY(c->isEnabled());
|
||||
}
|
||||
|
||||
// change version to an alternate one
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
|
||||
|
||||
auto removed = root->explicitlyRemovedCatalogs();
|
||||
auto j = std::find(removed.begin(), removed.end(),
|
||||
"org.flightgear.test.catalog-alt");
|
||||
SG_VERIFY(j != removed.end());
|
||||
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
// this would tirgger migration, but we blocked it
|
||||
root->refresh(true);
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
pkg::CatalogRef cat = root->getCatalogById("org.flightgear.test.catalog1");
|
||||
SG_VERIFY(!cat->isEnabled());
|
||||
SG_CHECK_EQUAL(cat->status(), pkg::Delegate::FAIL_VERSION);
|
||||
SG_CHECK_EQUAL(cat->id(), "org.flightgear.test.catalog1");
|
||||
SG_CHECK_EQUAL(cat->url(),
|
||||
"http://localhost:2000/catalogTest1/catalog.xml");
|
||||
|
||||
auto enabledCats = root->catalogs();
|
||||
auto it = std::find(enabledCats.begin(), enabledCats.end(), cat);
|
||||
SG_VERIFY(it == enabledCats.end());
|
||||
|
||||
// check the new catalog
|
||||
auto altCat = root->getCatalogById("org.flightgear.test.catalog-alt");
|
||||
SG_VERIFY(altCat.get() == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
sglog().setLogLevels( SG_ALL, SG_WARN );
|
||||
@@ -1208,27 +1333,31 @@ int main(int argc, char* argv[])
|
||||
testRefreshCatalog(&cl);
|
||||
|
||||
testInstallTarPackage(&cl);
|
||||
|
||||
|
||||
testInstallArchiveType(&cl);
|
||||
|
||||
|
||||
testDisableDueToVersion(&cl);
|
||||
|
||||
|
||||
testOfflineMode(&cl);
|
||||
|
||||
|
||||
testVersionMigrate(&cl);
|
||||
|
||||
|
||||
updateInvalidToValid(&cl);
|
||||
updateValidToInvalid(&cl);
|
||||
updateInvalidToInvalid(&cl);
|
||||
|
||||
|
||||
removeInvalidCatalog(&cl);
|
||||
|
||||
|
||||
testVersionMigrateToId(&cl);
|
||||
|
||||
testInstallBadPackage(&cl);
|
||||
|
||||
|
||||
testMirrorsFailure(&cl);
|
||||
|
||||
|
||||
testMigrateInstalled(&cl);
|
||||
|
||||
testDontMigrateRemoved(&cl);
|
||||
|
||||
cerr << "Successfully passed all tests!" << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace simgear {
|
||||
namespace pkg {
|
||||
|
||||
Package::Package(const SGPropertyNode* aProps, CatalogRef aCatalog) :
|
||||
m_catalog(aCatalog)
|
||||
m_catalog(aCatalog.get())
|
||||
{
|
||||
initWithProps(aProps);
|
||||
}
|
||||
@@ -198,6 +198,11 @@ InstallRef Package::install()
|
||||
{
|
||||
InstallRef ins = existingInstall();
|
||||
if (ins) {
|
||||
// if there's updates, treat this as a 'start update' request
|
||||
if (ins->hasUpdate()) {
|
||||
m_catalog->root()->scheduleToUpdate(ins);
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
@@ -210,13 +215,39 @@ InstallRef Package::install()
|
||||
return ins;
|
||||
}
|
||||
|
||||
InstallRef Package::markForInstall() {
|
||||
InstallRef ins = existingInstall();
|
||||
if (ins) {
|
||||
return ins;
|
||||
}
|
||||
|
||||
const auto pd = pathOnDisk();
|
||||
|
||||
Dir dir(pd);
|
||||
if (!dir.create(0700)) {
|
||||
SG_LOG(SG_IO, SG_ALERT,
|
||||
"Package::markForInstall: couldn't create directory at:" << pd);
|
||||
return {};
|
||||
}
|
||||
|
||||
ins = new Install{this, pd};
|
||||
_install_cb(this, ins); // not sure if we should trigger the callback for this
|
||||
|
||||
// repeat for dependencies to be kind
|
||||
for (auto dep : dependencies()) {
|
||||
dep->markForInstall();
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
InstallRef Package::existingInstall(const InstallCallback& cb) const
|
||||
{
|
||||
InstallRef install;
|
||||
try {
|
||||
install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this));
|
||||
} catch (std::exception& ) {
|
||||
return InstallRef();
|
||||
return {};
|
||||
}
|
||||
|
||||
if( cb )
|
||||
@@ -235,6 +266,11 @@ std::string Package::id() const
|
||||
return m_id;
|
||||
}
|
||||
|
||||
CatalogRef Package::catalog() const
|
||||
{
|
||||
return {m_catalog};
|
||||
}
|
||||
|
||||
std::string Package::qualifiedId() const
|
||||
{
|
||||
return m_catalog->id() + "." + id();
|
||||
|
||||
@@ -61,6 +61,13 @@ public:
|
||||
InstallRef
|
||||
existingInstall(const InstallCallback& cb = InstallCallback()) const;
|
||||
|
||||
/**
|
||||
* Mark this package for installation, but don't actually start the
|
||||
* download process. This creates the on-disk placeholder, so
|
||||
* the package will appear an eededing to be updated.
|
||||
*/
|
||||
InstallRef markForInstall();
|
||||
|
||||
bool isInstalled() const;
|
||||
|
||||
/**
|
||||
@@ -130,8 +137,7 @@ public:
|
||||
|
||||
size_t fileSizeBytes() const;
|
||||
|
||||
CatalogRef catalog() const
|
||||
{ return m_catalog; }
|
||||
CatalogRef catalog() const;
|
||||
|
||||
bool matches(const SGPropertyNode* aFilter) const;
|
||||
|
||||
@@ -236,7 +242,7 @@ private:
|
||||
SGPropertyNode_ptr m_props;
|
||||
std::string m_id;
|
||||
string_set m_tags;
|
||||
CatalogRef m_catalog;
|
||||
Catalog* m_catalog = nullptr; // non-owning ref, explicitly
|
||||
string_list m_variants;
|
||||
|
||||
mutable function_list<InstallCallback> _install_cb;
|
||||
|
||||
@@ -133,7 +133,9 @@ public:
|
||||
|
||||
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
|
||||
{
|
||||
for (auto d : delegates) {
|
||||
// take a copy of delegates since firing this can modify the data
|
||||
const auto currentDelegates = delegates;
|
||||
for (auto d : currentDelegates) {
|
||||
d->catalogRefreshed(catalog, status);
|
||||
}
|
||||
}
|
||||
@@ -283,6 +285,32 @@ public:
|
||||
fireDataForThumbnail(url, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
|
||||
}
|
||||
|
||||
void writeRemovedCatalogsFile() const {
|
||||
SGPath p = path / "RemovedCatalogs";
|
||||
sg_ofstream stream(p, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
for (const auto &cid : manuallyRemovedCatalogs) {
|
||||
stream << cid << "\n";
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
|
||||
void loadRemovedCatalogsFile() {
|
||||
manuallyRemovedCatalogs.clear();
|
||||
SGPath p = path / "RemovedCatalogs";
|
||||
if (!p.exists())
|
||||
return;
|
||||
|
||||
sg_ifstream stream(p, std::ios::in);
|
||||
while (!stream.eof()) {
|
||||
std::string line;
|
||||
std::getline(stream, line);
|
||||
const auto trimmed = strutils::strip(line);
|
||||
if (!trimmed.empty()) {
|
||||
manuallyRemovedCatalogs.push_back(trimmed);
|
||||
}
|
||||
} // of lines iteration
|
||||
}
|
||||
|
||||
DelegateVec delegates;
|
||||
|
||||
SGPath path;
|
||||
@@ -312,6 +340,9 @@ public:
|
||||
|
||||
typedef std::map<PackageRef, InstallRef> InstallCache;
|
||||
InstallCache m_installs;
|
||||
|
||||
/// persistent list of catalogs the user has manually removed
|
||||
string_list manuallyRemovedCatalogs;
|
||||
};
|
||||
|
||||
|
||||
@@ -400,6 +431,8 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
|
||||
thumbsCacheDir.create(0755);
|
||||
}
|
||||
|
||||
d->loadRemovedCatalogsFile();
|
||||
|
||||
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
|
||||
// note this will set the catalog status, which will insert into
|
||||
// disabled catalogs automatically if necesary
|
||||
@@ -621,6 +654,13 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
}
|
||||
}
|
||||
|
||||
void Root::scheduleAllUpdates() {
|
||||
auto toBeUpdated = packagesNeedingUpdate(); // make a copy
|
||||
for (const auto &u : toBeUpdated) {
|
||||
scheduleToUpdate(u->existingInstall());
|
||||
}
|
||||
}
|
||||
|
||||
bool Root::isInstallQueued(InstallRef aInstall) const
|
||||
{
|
||||
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
@@ -684,20 +724,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
|
||||
auto catIt = d->catalogs.find(aCat->id());
|
||||
d->fireRefreshStatus(aCat, aReason);
|
||||
|
||||
if (aReason == Delegate::STATUS_IN_PROGRESS) {
|
||||
d->refreshing.insert(aCat);
|
||||
} else {
|
||||
d->refreshing.erase(aCat);
|
||||
}
|
||||
|
||||
if ((aReason == Delegate::STATUS_REFRESHED) && (catIt == d->catalogs.end())) {
|
||||
if (aCat->isUserEnabled() &&
|
||||
(aReason == Delegate::STATUS_REFRESHED) &&
|
||||
(catIt == d->catalogs.end()))
|
||||
{
|
||||
assert(!aCat->id().empty());
|
||||
d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
|
||||
|
||||
// catalog might have been previously disabled, let's remove in that case
|
||||
// catalog might have been previously disabled, let's remove in that case
|
||||
auto j = std::find(d->disabledCatalogs.begin(),
|
||||
d->disabledCatalogs.end(),
|
||||
aCat);
|
||||
d->disabledCatalogs.end(),
|
||||
aCat);
|
||||
if (j != d->disabledCatalogs.end()) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id());
|
||||
d->disabledCatalogs.erase(j);
|
||||
@@ -705,7 +742,7 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
|
||||
}
|
||||
|
||||
if (!aCat->isEnabled()) {
|
||||
// catalog has errors, disable it
|
||||
// catalog has errors or was disabled by user, disable it
|
||||
auto j = std::find(d->disabledCatalogs.begin(),
|
||||
d->disabledCatalogs.end(),
|
||||
aCat);
|
||||
@@ -720,6 +757,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
|
||||
}
|
||||
} // of catalog is disabled
|
||||
|
||||
// remove from refreshing /after/ checking for enable / disabled, since for
|
||||
// new catalogs, the reference in d->refreshing might be our /only/
|
||||
// reference to the catalog. Once the refresh is done (either failed or
|
||||
// succeeded) the Catalog will be in either d->catalogs or
|
||||
// d->disabledCatalogs
|
||||
if (aReason == Delegate::STATUS_IN_PROGRESS) {
|
||||
d->refreshing.insert(aCat);
|
||||
} else {
|
||||
d->refreshing.erase(aCat);
|
||||
}
|
||||
|
||||
if (d->refreshing.empty()) {
|
||||
d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
|
||||
d->firePackagesChanged();
|
||||
@@ -780,6 +828,9 @@ bool Root::removeCatalogById(const std::string& aId)
|
||||
<< "failed to remove directory");
|
||||
}
|
||||
|
||||
d->manuallyRemovedCatalogs.push_back(aId);
|
||||
d->writeRemovedCatalogsFile();
|
||||
|
||||
// notify that a catalog is being removed
|
||||
d->firePackagesChanged();
|
||||
|
||||
@@ -851,6 +902,10 @@ void Root::unregisterInstall(InstallRef ins)
|
||||
d->fireFinishUninstall(ins->package());
|
||||
}
|
||||
|
||||
string_list Root::explicitlyRemovedCatalogs() const {
|
||||
return d->manuallyRemovedCatalogs;
|
||||
}
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -155,7 +155,22 @@ public:
|
||||
void requestThumbnailData(const std::string& aUrl);
|
||||
|
||||
bool isInstallQueued(InstallRef aInstall) const;
|
||||
private:
|
||||
|
||||
/**
|
||||
* Mark all 'to be updated' packages for update now
|
||||
*/
|
||||
void scheduleAllUpdates();
|
||||
|
||||
/**
|
||||
* @brief list of catalog IDs, the user has explicitly removed via
|
||||
* removeCatalogById(). This is important to allow the user to opt-out
|
||||
* of migrated packages.
|
||||
*
|
||||
* This information is stored in a helper file, in the root directory
|
||||
*/
|
||||
string_list explicitlyRemovedCatalogs() const;
|
||||
|
||||
private:
|
||||
friend class Install;
|
||||
friend class Catalog;
|
||||
friend class Package;
|
||||
|
||||
86
simgear/package/catalogTest2/catalog.xml
Normal file
86
simgear/package/catalogTest2/catalog.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<PropertyList>
|
||||
<id>org.flightgear.test.catalog2</id>
|
||||
<description>Second test catalog</description>
|
||||
<url>http://localhost:2000/catalogTest2/catalog.xml</url>
|
||||
<catalog-version>4</catalog-version>
|
||||
|
||||
<version>8.1.*</version>
|
||||
<version>8.0.0</version>
|
||||
<version>8.2.0</version>
|
||||
|
||||
<package>
|
||||
<id>alpha</id>
|
||||
<name>Alpha package</name>
|
||||
<revision type="int">8</revision>
|
||||
<file-size-bytes type="int">593</file-size-bytes>
|
||||
|
||||
<md5>a469c4b837f0521db48616cfe65ac1ea</md5>
|
||||
<url>http://localhost:2000/catalogTest1/alpha.zip</url>
|
||||
|
||||
<dir>alpha</dir>
|
||||
|
||||
</package>
|
||||
|
||||
|
||||
<package>
|
||||
<id>b737-NG</id>
|
||||
<name>Boeing 737 NG</name>
|
||||
<dir>b737NG</dir>
|
||||
<description>A popular twin-engined narrow body jet</description>
|
||||
<revision type="int">111</revision>
|
||||
<file-size-bytes type="int">860</file-size-bytes>
|
||||
|
||||
<tag>boeing</tag>
|
||||
<tag>jet</tag>
|
||||
<tag>ifr</tag>
|
||||
|
||||
<!-- not within a localized element -->
|
||||
<de>
|
||||
<description>German description of B737NG XYZ</description>
|
||||
</de>
|
||||
<fr>
|
||||
<description>French description of B737NG</description>
|
||||
</fr>
|
||||
|
||||
<rating>
|
||||
<FDM type="int">5</FDM>
|
||||
<systems type="int">5</systems>
|
||||
<model type="int">4</model>
|
||||
<cockpit type="int">4</cockpit>
|
||||
</rating>
|
||||
|
||||
<md5>a94ca5704f305b90767f40617d194ed6</md5>
|
||||
<url>http://localhost:2000/mirrorA/b737.tar.gz</url>
|
||||
<url>http://localhost:2000/mirrorB/b737.tar.gz</url>
|
||||
<url>http://localhost:2000/mirrorC/b737.tar.gz</url>
|
||||
</package>
|
||||
|
||||
<package>
|
||||
<id>b747-400</id>
|
||||
<name>Boeing 747-400</name>
|
||||
<dir>b744</dir>
|
||||
<description>A popular four-engined wide-body jet</description>
|
||||
<revision type="int">111</revision>
|
||||
<file-size-bytes type="int">860</file-size-bytes>
|
||||
|
||||
<tag>boeing</tag>
|
||||
<tag>jet</tag>
|
||||
<tag>ifr</tag>
|
||||
|
||||
|
||||
|
||||
<rating>
|
||||
<FDM type="int">5</FDM>
|
||||
<systems type="int">5</systems>
|
||||
<model type="int">4</model>
|
||||
<cockpit type="int">4</cockpit>
|
||||
</rating>
|
||||
|
||||
<md5>4d3f7417d74f811aa20ccc4f35673d20</md5>
|
||||
<!-- this URL will sometimes fail, on purpose -->
|
||||
<url>http://localhost:2000/catalogTest1/b747.tar.gz</url>
|
||||
</package>
|
||||
|
||||
</PropertyList>
|
||||
@@ -59,6 +59,12 @@ void AtomicChangeListener::fireChangeListeners()
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
void AtomicChangeListener::clearPendingChanges()
|
||||
{
|
||||
auto& listeners = ListenerListSingleton::instance()->listeners;
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
void AtomicChangeListener::valueChangedImplementation()
|
||||
{
|
||||
if (!_dirty) {
|
||||
|
||||
@@ -52,7 +52,17 @@ public:
|
||||
bool isDirty() { return _dirty; }
|
||||
bool isValid() { return _valid; }
|
||||
virtual void unregister_property(SGPropertyNode* node) override;
|
||||
|
||||
static void fireChangeListeners();
|
||||
|
||||
/**
|
||||
* @brief Ensure we've deleted any pending changes.
|
||||
*
|
||||
* This is important in shutdown and reset, to avoid holding
|
||||
* property listeners around after the property tree is destroyed
|
||||
*/
|
||||
static void clearPendingChanges();
|
||||
|
||||
private:
|
||||
virtual void valueChangedImplementation() override;
|
||||
virtual void valuesChanged();
|
||||
|
||||
@@ -535,7 +535,8 @@ readComparison( SGPropertyNode *prop_root,
|
||||
{
|
||||
SGComparisonCondition * condition = new SGComparisonCondition(type, reverse);
|
||||
if (node->nChildren() < 2 || node->nChildren() > 3 ) {
|
||||
throw sg_exception("condition: comparison without two or three children");
|
||||
throw sg_exception("condition: comparison without two or three children",
|
||||
{}, {}, false);
|
||||
}
|
||||
|
||||
const SGPropertyNode* left = node->getChild(0),
|
||||
@@ -551,7 +552,8 @@ readComparison( SGPropertyNode *prop_root,
|
||||
SGExpressiond* exp = SGReadDoubleExpression(prop_root, left->getChild(0));
|
||||
condition->setLeftDExpression(exp);
|
||||
} else {
|
||||
throw sg_exception("Unknown condition comparison left child:" + leftName);
|
||||
throw sg_exception("Unknown condition comparison left child:" + leftName,
|
||||
{}, {}, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +567,8 @@ readComparison( SGPropertyNode *prop_root,
|
||||
SGExpressiond* exp = SGReadDoubleExpression(prop_root, right->getChild(0));
|
||||
condition->setRightDExpression(exp);
|
||||
} else {
|
||||
throw sg_exception("Unknown condition comparison right child:" + rightName);
|
||||
throw sg_exception("Unknown condition comparison right child:" + rightName,
|
||||
{}, {}, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,7 +583,8 @@ readComparison( SGPropertyNode *prop_root,
|
||||
SGExpressiond* exp = SGReadDoubleExpression(prop_root, n->getChild(0));
|
||||
condition->setPrecisionDExpression(exp);
|
||||
} else {
|
||||
throw sg_exception("Unknown condition comparison precision child:" + name );
|
||||
throw sg_exception("Unknown condition comparison precision child:" + name,
|
||||
{}, {}, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <exception> // can't use sg_exception becuase of PROPS_STANDALONE
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -51,11 +55,15 @@ using namespace simgear;
|
||||
|
||||
struct SGPropertyNodeListeners
|
||||
{
|
||||
/* Protect _num_iterators and _items. We use a recursive mutex to allow
|
||||
nested access to work as normal. */
|
||||
std::recursive_mutex _rmutex;
|
||||
|
||||
/* This keeps a count of the current number of nested invocations of
|
||||
forEachListener(). If non-zero, other code higher up the stack is iterating
|
||||
_items[] so for example code must not erase items in the vector. */
|
||||
int _num_iterators = 0;
|
||||
|
||||
|
||||
std::vector<SGPropertyChangeListener *> _items;
|
||||
};
|
||||
|
||||
@@ -558,23 +566,15 @@ find_node (SGPropertyNode * current,
|
||||
}
|
||||
}
|
||||
#else
|
||||
template<typename Range>
|
||||
SGPropertyNode*
|
||||
find_node (SGPropertyNode * current,
|
||||
const Range& path,
|
||||
bool create,
|
||||
int last_index = -1)
|
||||
{
|
||||
template <typename Range>
|
||||
SGPropertyNode *find_node(SGPropertyNode *current, const Range &path,
|
||||
bool create, int last_index = -1) {
|
||||
using namespace boost;
|
||||
typedef split_iterator<typename range_result_iterator<Range>::type>
|
||||
PathSplitIterator;
|
||||
|
||||
PathSplitIterator itr
|
||||
= make_split_iterator(path, first_finder("/", is_equal()));
|
||||
auto itr = make_split_iterator(path, first_finder("/", is_equal()));
|
||||
if (*path.begin() == '/')
|
||||
return find_node_aux(current->getRootNode(), itr, create, last_index);
|
||||
else
|
||||
return find_node_aux(current, itr, create, last_index);
|
||||
else
|
||||
return find_node_aux(current, itr, create, last_index);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2406,6 +2406,7 @@ SGPropertyNode::addChangeListener (SGPropertyChangeListener * listener,
|
||||
if (_listeners == 0)
|
||||
_listeners = new SGPropertyNodeListeners;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
/* If there's a nullptr entry (a listener that was unregistered), we
|
||||
overwrite it. This ensures that listeners that routinely unregister+register
|
||||
themselves don't make _listeners->_items grow unnecessarily. Otherwise simply
|
||||
@@ -2429,9 +2430,13 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
|
||||
{
|
||||
if (_listeners == 0)
|
||||
return;
|
||||
/* We use a std::unique_lock rather than a std::lock_guard because we may
|
||||
need to unlock early. */
|
||||
std::unique_lock<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
vector<SGPropertyChangeListener*>::iterator it =
|
||||
find(_listeners->_items.begin(), _listeners->_items.end(), listener);
|
||||
if (it != _listeners->_items.end()) {
|
||||
assert(_listeners->_num_iterators >= 0);
|
||||
if (_listeners->_num_iterators) {
|
||||
/* _listeners._items is currently being iterated further up the stack in
|
||||
this thread by one or more nested invocations of forEachListener(), so
|
||||
@@ -2450,6 +2455,7 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
|
||||
_listeners->_items.erase(it);
|
||||
listener->unregister_property(this);
|
||||
if (_listeners->_items.empty()) {
|
||||
lock.unlock();
|
||||
delete _listeners;
|
||||
_listeners = 0;
|
||||
}
|
||||
@@ -2511,9 +2517,11 @@ static void forEachListener(
|
||||
)
|
||||
{
|
||||
if (!_listeners) return;
|
||||
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
assert(_listeners->_num_iterators >= 0);
|
||||
_listeners->_num_iterators += 1;
|
||||
|
||||
|
||||
/* We need to use an index here when iterating _listeners->_items, not an
|
||||
iterator. This is because a listener may add new listeners, causing the
|
||||
vector to be reallocated, which would invalidate any iterator. */
|
||||
@@ -2528,10 +2536,12 @@ static void forEachListener(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_listeners->_num_iterators -= 1;
|
||||
|
||||
assert(_listeners->_num_iterators >= 0);
|
||||
|
||||
if (_listeners->_num_iterators == 0) {
|
||||
|
||||
/* Remove any items that have been set to nullptr. */
|
||||
_listeners->_items.erase(
|
||||
std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr),
|
||||
@@ -2547,6 +2557,7 @@ static void forEachListener(
|
||||
int SGPropertyNode::nListeners() const
|
||||
{
|
||||
if (!_listeners) return 0;
|
||||
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
int n = 0;
|
||||
for (auto listener: _listeners->_items) {
|
||||
if (listener) n += 1;
|
||||
|
||||
@@ -2197,11 +2197,12 @@ public:
|
||||
_property->addChangeListener(this,initial);
|
||||
}
|
||||
|
||||
SGPropertyChangeCallback(const SGPropertyChangeCallback<T>& other) :
|
||||
_obj(other._obj), _callback(other._callback), _property(other._property)
|
||||
{
|
||||
_property->addChangeListener(this,false);
|
||||
}
|
||||
SGPropertyChangeCallback(const SGPropertyChangeCallback<T>& other)
|
||||
: SGPropertyChangeListener(other),
|
||||
_obj(other._obj), _callback(other._callback), _property(other._property)
|
||||
{
|
||||
_property->addChangeListener(this,false);
|
||||
}
|
||||
|
||||
virtual ~SGPropertyChangeCallback()
|
||||
{
|
||||
|
||||
@@ -161,7 +161,7 @@ setFlag( int& mode,
|
||||
string message = "Unrecognized flag value '";
|
||||
message += flag;
|
||||
message += '\'';
|
||||
throw sg_io_exception(message, location, SG_ORIGIN);
|
||||
throw sg_io_exception(message, location, SG_ORIGIN, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
|
||||
string message = "Root element name is ";
|
||||
message += name;
|
||||
message += "; expected PropertyList";
|
||||
throw sg_io_exception(message, location, SG_ORIGIN);
|
||||
throw sg_io_exception(message, location, SG_ORIGIN, false);
|
||||
}
|
||||
|
||||
// Check for an include.
|
||||
@@ -188,7 +188,7 @@ PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
|
||||
{
|
||||
string message ="Cannot open file ";
|
||||
message += attval;
|
||||
throw sg_io_exception(message, location, SG_ORIGIN);
|
||||
throw sg_io_exception(message, location, SG_ORIGIN, false);
|
||||
}
|
||||
readProperties(path, _root, 0, _extended);
|
||||
} catch (sg_io_exception &e) {
|
||||
@@ -278,7 +278,7 @@ PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
|
||||
{
|
||||
string message ="Cannot open file ";
|
||||
message += val;
|
||||
throw sg_io_exception(message, location, SG_ORIGIN);
|
||||
throw sg_io_exception(message, location, SG_ORIGIN, false);
|
||||
}
|
||||
readProperties(path, node, 0, _extended);
|
||||
}
|
||||
@@ -353,7 +353,7 @@ PropsVisitor::endElement (const char * name)
|
||||
string message = "Unrecognized data type '";
|
||||
message += st.type;
|
||||
message += '\'';
|
||||
throw sg_io_exception(message, location, SG_ORIGIN);
|
||||
throw sg_io_exception(message, location, SG_ORIGIN, false);
|
||||
}
|
||||
if( !ret )
|
||||
SG_LOG
|
||||
@@ -701,7 +701,7 @@ writeProperties (const SGPath &path, const SGPropertyNode * start_node,
|
||||
if (output.good()) {
|
||||
writeProperties(output, start_node, write_all, archive_flag);
|
||||
} else {
|
||||
throw sg_io_exception("Cannot open file", sg_location(path.utf8Str()));
|
||||
throw sg_io_exception("Cannot open file", sg_location(path.utf8Str()), "", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +770,7 @@ copyPropertyValue(const SGPropertyNode *in, SGPropertyNode *out)
|
||||
break;
|
||||
string message = "Unknown internal SGPropertyNode type";
|
||||
message += in->getType();
|
||||
throw sg_error(message, SG_ORIGIN);
|
||||
throw sg_error(message, SG_ORIGIN, false);
|
||||
}
|
||||
|
||||
return retval;
|
||||
|
||||
@@ -67,16 +67,21 @@
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
#include <simgear/scene/tgdb/userdata.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/SGProgram.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/scene/util/SGSceneFeatures.hxx>
|
||||
#include <simgear/scene/util/StateAttributeFactory.hxx>
|
||||
#include <simgear/structure/OSGUtils.hxx>
|
||||
#include <simgear/structure/SGExpression.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
|
||||
namespace simgear
|
||||
@@ -103,6 +108,7 @@ bool loadShaderFromUTF8File(osg::Shader* shader, const std::string& fileName)
|
||||
if (!inStream.is_open())
|
||||
return false;
|
||||
|
||||
shader->setFileName(fileName);
|
||||
shader->setShaderSource(inStream.read_all());
|
||||
return true;
|
||||
}
|
||||
@@ -221,8 +227,9 @@ Effect::Effect()
|
||||
}
|
||||
|
||||
Effect::Effect(const Effect& rhs, const CopyOp& copyop)
|
||||
: osg::Object(rhs,copyop), root(rhs.root), parametersProp(rhs.parametersProp), _cache(0),
|
||||
_isRealized(rhs._isRealized)
|
||||
: osg::Object(rhs, copyop), root(rhs.root), parametersProp(rhs.parametersProp), _cache(0),
|
||||
_isRealized(rhs._isRealized),
|
||||
_effectFilePath(rhs._effectFilePath)
|
||||
{
|
||||
typedef vector<ref_ptr<Technique> > TechniqueList;
|
||||
for (TechniqueList::const_iterator itr = rhs.techniques.begin(),
|
||||
@@ -287,6 +294,8 @@ Effect::~Effect()
|
||||
void buildPass(Effect* effect, Technique* tniq, const SGPropertyNode* prop,
|
||||
const SGReaderWriterOptions* options)
|
||||
{
|
||||
simgear::ErrorReportContext ec("effect-pass", prop->getPath());
|
||||
|
||||
Pass* pass = new Pass;
|
||||
tniq->passes.push_back(pass);
|
||||
for (int i = 0; i < prop->nChildren(); ++i) {
|
||||
@@ -837,10 +846,12 @@ void reload_shaders()
|
||||
string fileName = SGModelLib::findDataFile(sitr->first.first);
|
||||
if (!fileName.empty()) {
|
||||
loadShaderFromUTF8File(shader, fileName);
|
||||
}
|
||||
else
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could not locate shader: " << fileName);
|
||||
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound,
|
||||
simgear::ErrorCode::LoadEffectsShaders,
|
||||
"Reload: couldn't find shader:" + sitr->first.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,15 +939,16 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
// FIXME orig: const string& shaderName = shaderKey.first;
|
||||
string shaderName = shaderKey.first;
|
||||
Shader::Type stype = (Shader::Type)shaderKey.second;
|
||||
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false) &&
|
||||
const bool compositorEnabled = getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false);
|
||||
if (compositorEnabled &&
|
||||
shaderName.substr(0, shaderName.find("/")) == "Shaders") {
|
||||
shaderName = "Compositor/" + shaderName;
|
||||
}
|
||||
string fileName = SGModelLib::findDataFile(shaderName, options);
|
||||
if (fileName.empty())
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could not locate shader" << shaderName);
|
||||
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::LoadEffectsShaders,
|
||||
"Couldn't locate shader:" + shaderName, sg_location{shaderName});
|
||||
|
||||
throw BuilderException(string("couldn't find shader ") +
|
||||
shaderName);
|
||||
@@ -950,7 +962,11 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
pass->setAttributeAndModes(program);
|
||||
return;
|
||||
}
|
||||
program = new Program;
|
||||
|
||||
auto sgprogram = new SGProgram;
|
||||
program = sgprogram;
|
||||
sgprogram->setEffectFilePath(effect->filePath());
|
||||
|
||||
for (const auto& skey : resolvedKey.shaders) {
|
||||
const string& fileName = skey.first;
|
||||
Shader::Type stype = (Shader::Type)skey.second;
|
||||
@@ -961,11 +977,30 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
ref_ptr<Shader> shader = new Shader(stype);
|
||||
shader->setName(fileName);
|
||||
if (loadShaderFromUTF8File(shader, fileName)) {
|
||||
program->addShader(shader.get());
|
||||
if (!program->addShader(shader.get())) {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData,
|
||||
simgear::ErrorCode::LoadEffectsShaders,
|
||||
"Program::addShader failed",
|
||||
SGPath::fromUtf8(fileName));
|
||||
}
|
||||
|
||||
shaderMap.insert(ShaderMap::value_type(skey, shader));
|
||||
} else {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData,
|
||||
simgear::ErrorCode::LoadEffectsShaders,
|
||||
"Failed to read shader source code",
|
||||
SGPath::fromUtf8(fileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sgprogram->getNumShaders() == 0) {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData,
|
||||
simgear::ErrorCode::LoadEffectsShaders,
|
||||
"No shader source code defined for effect",
|
||||
effect->filePath());
|
||||
}
|
||||
|
||||
for (const auto& key : prgKey.attributes) {
|
||||
program->addBindAttribLocation(key.first, key.second);
|
||||
}
|
||||
@@ -1326,6 +1361,8 @@ InstallAttributeBuilder<DepthBuilder> installDepth("depth");
|
||||
void buildTechnique(Effect* effect, const SGPropertyNode* prop,
|
||||
const SGReaderWriterOptions* options)
|
||||
{
|
||||
simgear::ErrorReportContext ec("effect-technique", prop->getPath());
|
||||
|
||||
Technique* tniq = new Technique;
|
||||
effect->techniques.push_back(tniq);
|
||||
tniq->setScheme(prop->getStringValue("scheme"));
|
||||
@@ -1481,20 +1518,21 @@ void mergeSchemesFallbacks(Effect *effect, const SGReaderWriterOptions *options)
|
||||
|
||||
// Walk the techniques property tree, building techniques and
|
||||
// passes.
|
||||
static std::mutex realizeTechniques_lock;
|
||||
bool Effect::realizeTechniques(const SGReaderWriterOptions* options)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(realizeTechniques_lock);
|
||||
simgear::ErrorReportContext ec{"effect", getName()};
|
||||
|
||||
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false))
|
||||
mergeSchemesFallbacks(this, options);
|
||||
|
||||
if (_isRealized)
|
||||
return true;
|
||||
|
||||
PropertyList tniqList = root->getChildren("technique");
|
||||
for (PropertyList::iterator itr = tniqList.begin(), e = tniqList.end();
|
||||
itr != e;
|
||||
++itr)
|
||||
buildTechnique(this, *itr, options);
|
||||
for (const auto& tniq : tniqList) {
|
||||
buildTechnique(this, tniq, options);
|
||||
}
|
||||
|
||||
_isRealized = true;
|
||||
return true;
|
||||
}
|
||||
@@ -1630,3 +1668,15 @@ expression::ExpParserRegistrar propvalueRegistrar("float-property",
|
||||
propertyExpressionParser<float>);
|
||||
|
||||
}
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
void Effect::setFilePath(const SGPath& path)
|
||||
{
|
||||
_effectFilePath = path;
|
||||
}
|
||||
|
||||
SGPath Effect::filePath() const
|
||||
{
|
||||
return _effectFilePath;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
#include <osg/observer_ptr>
|
||||
#include <osgDB/ReaderWriter>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/scene/util/UpdateOnceCallback.hxx>
|
||||
#include <simgear/threads/SGThread.hxx>
|
||||
#include <simgear/structure/Singleton.hxx>
|
||||
#include <simgear/threads/SGThread.hxx>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
@@ -106,6 +107,10 @@ public:
|
||||
|
||||
std::string getName(){return _name;}
|
||||
void setName(std::string name){_name = name;}
|
||||
|
||||
void setFilePath(const SGPath& path);
|
||||
SGPath filePath() const;
|
||||
|
||||
protected:
|
||||
~Effect();
|
||||
// Support for a cache of effects that inherit from this one, so
|
||||
@@ -142,10 +147,13 @@ protected:
|
||||
Cache* _cache;
|
||||
friend size_t hash_value(const Key& key);
|
||||
friend Effect* makeEffect(SGPropertyNode* prop, bool realizeTechniques,
|
||||
const SGReaderWriterOptions* options);
|
||||
const SGReaderWriterOptions* options,
|
||||
const SGPath& path);
|
||||
bool _isRealized;
|
||||
std::string _name;
|
||||
SGPath _effectFilePath;
|
||||
};
|
||||
|
||||
// Automatic support for boost hash function
|
||||
size_t hash_value(const Effect::Key&);
|
||||
|
||||
@@ -156,7 +164,8 @@ Effect* makeEffect(const std::string& name,
|
||||
|
||||
Effect* makeEffect(SGPropertyNode* prop,
|
||||
bool realizeTechniques,
|
||||
const SGReaderWriterOptions* options);
|
||||
const SGReaderWriterOptions* options,
|
||||
const SGPath& path = SGPath{});
|
||||
|
||||
bool makeParametersFromStateSet(SGPropertyNode* paramRoot,
|
||||
const osg::StateSet* ss);
|
||||
|
||||
@@ -75,13 +75,13 @@ BuilderException::BuilderException()
|
||||
}
|
||||
|
||||
BuilderException::BuilderException(const char* message, const char* origin)
|
||||
: sg_exception(message, origin)
|
||||
: sg_exception(message, origin, {}, false)
|
||||
{
|
||||
}
|
||||
|
||||
BuilderException::BuilderException(const std::string& message,
|
||||
const std::string& origin)
|
||||
: sg_exception(message, origin)
|
||||
: sg_exception(message, origin, {}, false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -39,12 +39,13 @@
|
||||
#include <OpenThreads/Mutex>
|
||||
#include <OpenThreads/ScopedLock>
|
||||
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/scene/util/SGSceneFeatures.hxx>
|
||||
#include <simgear/scene/util/StateAttributeFactory.hxx>
|
||||
#include <simgear/structure/OSGUtils.hxx>
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
@@ -273,11 +274,20 @@ bool setAttrs(const TexTuple& attrs, Texture* tex,
|
||||
options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS_NORMALIZED);
|
||||
else
|
||||
options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS);
|
||||
#if OSG_VERSION_LESS_THAN(3,4,2)
|
||||
result = osgDB::readImageFile(imageName, options);
|
||||
#else
|
||||
result = osgDB::readRefImageFile(imageName, options);
|
||||
#endif
|
||||
|
||||
try {
|
||||
#if OSG_VERSION_LESS_THAN(3,4,2)
|
||||
result = osgDB::readImageFile(imageName, options);
|
||||
#else
|
||||
result = osgDB::readRefImageFile(imageName, options);
|
||||
#endif
|
||||
} catch (std::exception& e) {
|
||||
simgear::reportFailure(simgear::LoadFailure::OutOfMemory, simgear::ErrorCode::LoadingTexture,
|
||||
string{"osgDB::readRefImageFile failed:"} + e.what(),
|
||||
SGPath::fromUtf8(imageName));
|
||||
return false;
|
||||
}
|
||||
|
||||
options->setLoadOriginHint(origLOH);
|
||||
osg::ref_ptr<osg::Image> image;
|
||||
if (result.success())
|
||||
@@ -296,6 +306,9 @@ bool setAttrs(const TexTuple& attrs, Texture* tex,
|
||||
tex->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter());
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " << imageName);
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::LoadingTexture,
|
||||
"osgDB::readRefImageFile failed:" + result.message(),
|
||||
SGPath::fromUtf8(imageName));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -580,8 +593,8 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode*
|
||||
const SGPropertyNode* texturesProp = getEffectPropertyChild(effect, props, "images");
|
||||
const SGPropertyNode* crossProp = getEffectPropertyChild(effect, props, "image");
|
||||
if (!texturesProp && !crossProp) {
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::LoadingTexture, "No images defined for cube map");
|
||||
throw BuilderException("no images defined for cube map");
|
||||
return NULL; // This is redundant
|
||||
}
|
||||
|
||||
// Using 6 separate images
|
||||
@@ -762,6 +775,9 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode*
|
||||
return cubeTexture.release();
|
||||
|
||||
} else {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::LoadingTexture,
|
||||
"Could not load cube-map image:" + result.message(),
|
||||
sg_location{texname});
|
||||
throw BuilderException("Could not load cube cross");
|
||||
}
|
||||
}
|
||||
@@ -842,6 +858,9 @@ Texture* Texture3DBuilder::build(Effect* effect, Pass* pass,
|
||||
tex->setImage(image3d.get());
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " << imageName);
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::LoadingTexture,
|
||||
"osgDB::readRefImageFile failed:" + result.message(),
|
||||
SGPath::fromUtf8(imageName));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/scene/util/SGSceneFeatures.hxx>
|
||||
#include <simgear/scene/util/SplicingVisitor.hxx>
|
||||
#include <simgear/structure/SGExpression.hxx>
|
||||
@@ -126,20 +128,20 @@ Effect* makeEffect(const string& name,
|
||||
string absFileName
|
||||
= SGModelLib::findDataFile(effectFileName, options);
|
||||
if (absFileName.empty()) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "can't find \"" << effectFileName << "\"");
|
||||
return 0;
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::LoadEffectsShaders, "Couldn't find Effect:" + effectFileName);
|
||||
return nullptr;
|
||||
}
|
||||
SGPropertyNode_ptr effectProps = new SGPropertyNode();
|
||||
try {
|
||||
readProperties(absFileName, effectProps.ptr(), 0, true);
|
||||
}
|
||||
catch (sg_io_exception& e) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "error reading \"" << absFileName << "\": "
|
||||
<< e.getFormattedMessage());
|
||||
return 0;
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::LoadEffectsShaders, e.getFormattedMessage(),
|
||||
e.getLocation());
|
||||
return nullptr;
|
||||
}
|
||||
ref_ptr<Effect> result = makeEffect(effectProps.ptr(), realizeTechniques,
|
||||
options);
|
||||
options, SGPath::fromUtf8(absFileName));
|
||||
if (result.valid()) {
|
||||
OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(effectMutex);
|
||||
pair<EffectMap::iterator, bool> irslt
|
||||
@@ -156,7 +158,8 @@ Effect* makeEffect(const string& name,
|
||||
|
||||
Effect* makeEffect(SGPropertyNode* prop,
|
||||
bool realizeTechniques,
|
||||
const SGReaderWriterOptions* options)
|
||||
const SGReaderWriterOptions* options,
|
||||
const SGPath& filePath)
|
||||
{
|
||||
// Give default names to techniques and passes
|
||||
vector<SGPropertyNode_ptr> techniques = prop->getChildren("technique");
|
||||
@@ -217,6 +220,7 @@ Effect* makeEffect(SGPropertyNode* prop,
|
||||
if (!effect.valid()) {
|
||||
effect = new Effect;
|
||||
effect->setName(nameProp->getStringValue());
|
||||
effect->setFilePath(filePath.isNull() ? parent->filePath() : filePath);
|
||||
effect->root = new SGPropertyNode;
|
||||
mergePropertyTrees(effect->root, prop, parent->root);
|
||||
effect->parametersProp = effect->root->getChild("parameters");
|
||||
@@ -234,12 +238,13 @@ Effect* makeEffect(SGPropertyNode* prop,
|
||||
effect->generator = parent->generator; // Copy the generators
|
||||
}
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "can't find base effect " <<
|
||||
inheritProp->getStringValue());
|
||||
return 0;
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::LoadEffectsShaders,
|
||||
string{"couldn't find base effect to inherit from:"} + inheritProp->getStringValue(), filePath);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
effect = new Effect;
|
||||
effect->setFilePath(filePath);
|
||||
effect->setName(nameProp->getStringValue());
|
||||
effect->root = prop;
|
||||
effect->parametersProp = effect->root->getChild("parameters");
|
||||
@@ -266,9 +271,9 @@ Effect* makeEffect(SGPropertyNode* prop,
|
||||
effect->realizeTechniques(options);
|
||||
}
|
||||
catch (BuilderException& e) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Error building technique: "
|
||||
<< e.getFormattedMessage());
|
||||
return 0;
|
||||
simgear::reportFailure(simgear::LoadFailure::Misconfigured, simgear::ErrorCode::LoadEffectsShaders,
|
||||
"Failed to build technique:" + e.getFormattedMessage(), filePath);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return effect.release();
|
||||
|
||||
@@ -97,11 +97,13 @@ SGMaterial::SGMaterial( const SGReaderWriterOptions* options,
|
||||
const SGPropertyNode *props,
|
||||
SGPropertyNode *prop_root,
|
||||
AreaList *a,
|
||||
SGSharedPtr<const SGCondition> c)
|
||||
SGSharedPtr<const SGCondition> c,
|
||||
const std::string& n)
|
||||
{
|
||||
init();
|
||||
areas = a;
|
||||
condition = c;
|
||||
region = n;
|
||||
read_properties( options, props, prop_root );
|
||||
buildEffectProperties(options);
|
||||
}
|
||||
@@ -110,12 +112,14 @@ SGMaterial::SGMaterial( const osgDB::Options* options,
|
||||
const SGPropertyNode *props,
|
||||
SGPropertyNode *prop_root,
|
||||
AreaList *a,
|
||||
SGSharedPtr<const SGCondition> c)
|
||||
SGSharedPtr<const SGCondition> c,
|
||||
const std::string& n)
|
||||
{
|
||||
osg::ref_ptr<SGReaderWriterOptions> opt;
|
||||
opt = SGReaderWriterOptions::copyOrCreate(options);
|
||||
areas = a;
|
||||
condition = c;
|
||||
region = n;
|
||||
init();
|
||||
read_properties(opt.get(), props, prop_root);
|
||||
buildEffectProperties(opt.get());
|
||||
|
||||
@@ -97,14 +97,16 @@ public:
|
||||
const SGPropertyNode *props,
|
||||
SGPropertyNode *prop_root,
|
||||
AreaList *a,
|
||||
SGSharedPtr<const SGCondition> c);
|
||||
SGSharedPtr<const SGCondition> c,
|
||||
const std::string& n);
|
||||
|
||||
|
||||
SGMaterial(const simgear::SGReaderWriterOptions*,
|
||||
const SGPropertyNode *props,
|
||||
SGPropertyNode *prop_root,
|
||||
AreaList *a,
|
||||
SGSharedPtr<const SGCondition> c);
|
||||
SGSharedPtr<const SGCondition> c,
|
||||
const std::string& n);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
@@ -123,6 +125,11 @@ public:
|
||||
simgear::Effect* get_one_effect(int texIndex);
|
||||
simgear::Effect* get_effect();
|
||||
|
||||
/**
|
||||
* Get the region Name.
|
||||
*/
|
||||
const std::string get_region_name() const { return region; }
|
||||
|
||||
/**
|
||||
* Get the Effect Name.
|
||||
*/
|
||||
@@ -487,6 +494,9 @@ private:
|
||||
SGVec4f ambient, diffuse, specular, emission;
|
||||
double shininess;
|
||||
|
||||
// region of this material
|
||||
std::string region;
|
||||
|
||||
// effect for this material
|
||||
std::string effect;
|
||||
|
||||
|
||||
@@ -128,12 +128,13 @@ bool SGMaterialLib::load( const SGPath &fg_root, const SGPath& mpath,
|
||||
|
||||
// Now build all the materials for this set of areas and conditions
|
||||
|
||||
const std::string region = node->getStringValue("name");
|
||||
const simgear::PropertyList materials = node->getChildren("material");
|
||||
simgear::PropertyList::const_iterator materials_iter = materials.begin();
|
||||
for (; materials_iter != materials.end(); materials_iter++) {
|
||||
const SGPropertyNode *node = materials_iter->get();
|
||||
SGSharedPtr<SGMaterial> m =
|
||||
new SGMaterial(options.get(), node, prop_root, arealist, condition);
|
||||
new SGMaterial(options.get(), node, prop_root, arealist, condition, region);
|
||||
|
||||
std::vector<SGPropertyNode_ptr>names = node->getChildren("name");
|
||||
for ( unsigned int j = 0; j < names.size(); j++ ) {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <osg/AlphaFunc>
|
||||
#include <osg/Group>
|
||||
@@ -103,37 +103,39 @@ SGMatModel::get_model_count( SGPropertyNode *prop_root )
|
||||
inline void
|
||||
SGMatModel::load_models( SGPropertyNode *prop_root )
|
||||
{
|
||||
// Load model only on demand
|
||||
if (!_models_loaded) {
|
||||
for (unsigned int i = 0; i < _paths.size(); i++) {
|
||||
osg::Node *entity = SGModelLib::loadModel(_paths[i], prop_root);
|
||||
if (entity != 0) {
|
||||
// FIXME: this stuff can be handled
|
||||
// in the XML wrapper as well (at least,
|
||||
// the billboarding should be handled
|
||||
// there).
|
||||
|
||||
if (_heading_type == HEADING_BILLBOARD) {
|
||||
// if the model is a billboard, it is likely :
|
||||
// 1. a branch with only leaves,
|
||||
// 2. a tree or a non rectangular shape faked by transparency
|
||||
// We add alpha clamp then
|
||||
osg::StateSet* stateSet = entity->getOrCreateStateSet();
|
||||
osg::AlphaFunc* alphaFunc =
|
||||
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01f);
|
||||
stateSet->setAttributeAndModes(alphaFunc,
|
||||
osg::StateAttribute::OVERRIDE);
|
||||
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||
}
|
||||
|
||||
_models.push_back(entity);
|
||||
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
|
||||
// Ensure the vector contains something, otherwise get_random_model below fails
|
||||
_models.push_back(new osg::Node());
|
||||
}
|
||||
}
|
||||
std::lock_guard<std::mutex> g(_loadMutex);
|
||||
|
||||
// Load model only on demand
|
||||
if (!_models_loaded) {
|
||||
for (unsigned int i = 0; i < _paths.size(); i++) {
|
||||
osg::Node* entity = SGModelLib::loadModel(_paths[i], prop_root);
|
||||
if (entity != 0) {
|
||||
// FIXME: this stuff can be handled
|
||||
// in the XML wrapper as well (at least,
|
||||
// the billboarding should be handled
|
||||
// there).
|
||||
|
||||
if (_heading_type == HEADING_BILLBOARD) {
|
||||
// if the model is a billboard, it is likely :
|
||||
// 1. a branch with only leaves,
|
||||
// 2. a tree or a non rectangular shape faked by transparency
|
||||
// We add alpha clamp then
|
||||
osg::StateSet* stateSet = entity->getOrCreateStateSet();
|
||||
osg::AlphaFunc* alphaFunc =
|
||||
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01f);
|
||||
stateSet->setAttributeAndModes(alphaFunc,
|
||||
osg::StateAttribute::OVERRIDE);
|
||||
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||
}
|
||||
|
||||
_models.push_back(entity);
|
||||
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
|
||||
// Ensure the vector contains something, otherwise get_random_model below fails
|
||||
_models.push_back(new osg::Node());
|
||||
}
|
||||
}
|
||||
}
|
||||
_models_loaded = true;
|
||||
}
|
||||
|
||||
@@ -149,6 +149,8 @@ private:
|
||||
double _spacing_m;
|
||||
double _range_m;
|
||||
HeadingType _heading_type;
|
||||
|
||||
std::mutex _loadMutex;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -82,13 +82,13 @@ void _writeColor(GLenum pixelFormat, T* data, float scale, osg::Vec4 value)
|
||||
switch(pixelFormat)
|
||||
{
|
||||
case(GL_DEPTH_COMPONENT): //intentionally fall through and execute the code for GL_LUMINANCE
|
||||
case(GL_LUMINANCE): { *data++ = value.r()*scale; break; }
|
||||
case(GL_ALPHA): { *data++ = value.a()*scale; break; }
|
||||
case(GL_LUMINANCE_ALPHA): { *data++ = value.r()*scale; *data++ = value.a()*scale; break; }
|
||||
case(GL_RGB): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; break; }
|
||||
case(GL_RGBA): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; *data++ = value.a()*scale; break; }
|
||||
case(GL_BGR): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; break; }
|
||||
case(GL_BGRA): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; *data++ = value.a()*scale; break; }
|
||||
case(GL_LUMINANCE): { *data = value.r()*scale; break; }
|
||||
case(GL_ALPHA): { *data = value.a()*scale; break; }
|
||||
case(GL_LUMINANCE_ALPHA): { *data++ = value.r()*scale; *data = value.a()*scale; break; }
|
||||
case(GL_RGB): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data = value.b()*scale; break; }
|
||||
case(GL_RGBA): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; *data = value.a()*scale; break; }
|
||||
case(GL_BGR): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data = value.r()*scale; break; }
|
||||
case(GL_BGRA): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; *data = value.a()*scale; break; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,13 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
|
||||
{
|
||||
bool computeMipmap = false;
|
||||
unsigned int nbComponents = osg::Image::computeNumComponents( image->getPixelFormat() );
|
||||
if ( std::get<0>(attrs) != AUTOMATIC &&
|
||||
int s = image->s();
|
||||
int t = image->t();
|
||||
if ( (s & (s - 1)) || (t & (t - 1)) ) // power of two test
|
||||
{
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "mipmapping: texture size not a power-of-two: " + image->getFileName());
|
||||
}
|
||||
else if ( std::get<0>(attrs) != AUTOMATIC &&
|
||||
( std::get<1>(attrs) != AUTOMATIC || nbComponents < 2 ) &&
|
||||
( std::get<2>(attrs) != AUTOMATIC || nbComponents < 3 ) &&
|
||||
( std::get<3>(attrs) != AUTOMATIC || nbComponents < 4 ) )
|
||||
@@ -283,9 +289,7 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
|
||||
if ( computeMipmap )
|
||||
{
|
||||
osg::ref_ptr<osg::Image> mipmaps = new osg::Image();
|
||||
int s = image->s(),
|
||||
t = image->t(),
|
||||
r = image->r();
|
||||
int r = image->r();
|
||||
int nb = osg::Image::computeNumberOfMipmapLevels(s, t, r);
|
||||
osg::Image::MipmapDataType mipmapOffsets;
|
||||
unsigned int offset = 0;
|
||||
|
||||
@@ -56,12 +56,15 @@
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
|
||||
#include <simgear/io/sg_file.hxx>
|
||||
#include <simgear/misc/lru_cache.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "BoundingVolumeBuildVisitor.hxx"
|
||||
#include "model.hxx"
|
||||
@@ -289,6 +292,11 @@ ModelRegistry::readImage(const string& fileName,
|
||||
|
||||
const SGReaderWriterOptions* sgoptC = dynamic_cast<const SGReaderWriterOptions*>(opt);
|
||||
|
||||
simgear::ErrorReportContext ec;
|
||||
if (sgoptC && sgoptC->getModelData()) {
|
||||
ec.addFromMap(sgoptC->getModelData()->getErrorContext());
|
||||
}
|
||||
|
||||
if (cache_active && (!sgoptC || sgoptC->getLoadOriginHint() != SGReaderWriterOptions::LoadOriginHint::ORIGIN_SPLASH_SCREEN)) {
|
||||
if (fileExtension != "dds" && fileExtension != "gz") {
|
||||
|
||||
@@ -359,6 +367,8 @@ ModelRegistry::readImage(const string& fileName,
|
||||
if (res.validImage()) {
|
||||
osg::ref_ptr<osg::Image> srcImage = res.getImage();
|
||||
int width = srcImage->s();
|
||||
//int packing = srcImage->getPacking();
|
||||
//printf("packing %d format %x pixel size %d InternalTextureFormat %x\n", packing, srcImage->getPixelFormat(), srcImage->getPixelSizeInBits(), srcImage->getInternalTextureFormat() );
|
||||
bool transparent = srcImage->isImageTranslucent();
|
||||
bool isNormalMap = false;
|
||||
bool isEffect = false;
|
||||
@@ -367,6 +377,11 @@ ModelRegistry::readImage(const string& fileName,
|
||||
*/
|
||||
bool can_compress = (transparent && compress_transparent) || (!transparent && compress_solid);
|
||||
|
||||
if (srcImage->getPixelSizeInBits() <= 16) {
|
||||
SG_LOG(SG_IO, SG_INFO, "Ignoring " + absFileName + " for inclusion into the texture cache because pixel density too low at " << srcImage->getPixelSizeInBits() << " bits per pixek");
|
||||
can_compress = false;
|
||||
}
|
||||
|
||||
int height = srcImage->t();
|
||||
|
||||
// use the new file origin to determine any special processing
|
||||
@@ -386,6 +401,10 @@ ModelRegistry::readImage(const string& fileName,
|
||||
isEffect = true;
|
||||
// can_compress = false;
|
||||
}
|
||||
else if (sgoptC && !transparent && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_CANVAS) {
|
||||
SG_LOG(SG_IO, SG_INFO, "From Canvas " + absFileName + " will generate mipmap only");
|
||||
can_compress = false;
|
||||
}
|
||||
if (can_compress)
|
||||
{
|
||||
std::string pot_message;
|
||||
@@ -538,7 +557,13 @@ ModelRegistry::readImage(const string& fileName,
|
||||
}
|
||||
}
|
||||
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
try {
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
} catch (std::bad_alloc&) {
|
||||
simgear::reportFailure(simgear::LoadFailure::OutOfMemory, simgear::ErrorCode::ThreeDModelLoad,
|
||||
"Out of memory loading texture", sg_location{absFileName});
|
||||
return ReaderWriter::ReadResult::INSUFFICIENT_MEMORY_TO_LOAD;
|
||||
}
|
||||
|
||||
if (!res.success()) {
|
||||
SG_LOG(SG_IO, SG_DEV_WARN, "Image loading failed:" << res.message());
|
||||
@@ -703,7 +728,17 @@ ReaderWriter::ReadResult
|
||||
ModelRegistry::readNode(const string& fileName,
|
||||
const Options* opt)
|
||||
{
|
||||
ReaderWriter::ReadResult res;
|
||||
// propogate error context from the caller
|
||||
simgear::ErrorReportContext ec;
|
||||
auto sgopt = dynamic_cast<const SGReaderWriterOptions*>(opt);
|
||||
if (sgopt) {
|
||||
if (sgopt->getModelData()) {
|
||||
ec.addFromMap(sgopt->getModelData()->getErrorContext());
|
||||
}
|
||||
|
||||
ec.addFromMap(sgopt->getErrorContext());
|
||||
}
|
||||
|
||||
CallbackMap::iterator iter
|
||||
= nodeCallbackMap.find(getFileExtension(fileName));
|
||||
ReaderWriter::ReadResult result;
|
||||
@@ -712,6 +747,11 @@ ModelRegistry::readNode(const string& fileName,
|
||||
else
|
||||
result = _defaultCallback->readNode(fileName, opt);
|
||||
|
||||
if (!result.validNode()) {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ThreeDModelLoad,
|
||||
"Failed to load 3D model:" + result.message(), sg_location{fileName});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,11 +136,19 @@ osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter& ea)
|
||||
|
||||
virtual void update(double dt, int keyModState)
|
||||
{
|
||||
if (!_condition || _condition->test()) {
|
||||
SG_UNUSED(keyModState);
|
||||
if (!_repeatable)
|
||||
return;
|
||||
SG_UNUSED(keyModState);
|
||||
if (_condition && !_condition->test()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_repeatable)
|
||||
return;
|
||||
|
||||
const bool zeroInterval = (_repeatInterval <= 0.0);
|
||||
if (zeroInterval) {
|
||||
// fire once per frame
|
||||
fireBindingList(_bindingsDown);
|
||||
} else {
|
||||
_repeatTime += dt;
|
||||
while (_repeatInterval < _repeatTime) {
|
||||
_repeatTime -= _repeatInterval;
|
||||
@@ -678,12 +686,18 @@ public:
|
||||
if (_hasDragged) {
|
||||
return;
|
||||
}
|
||||
|
||||
_repeatTime += dt;
|
||||
while (_repeatInterval < _repeatTime) {
|
||||
_repeatTime -= _repeatInterval;
|
||||
|
||||
const bool zeroInterval = (_repeatInterval <= 0.0);
|
||||
if (zeroInterval) {
|
||||
// fire once per frame
|
||||
fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
|
||||
} // of repeat iteration
|
||||
} else {
|
||||
_repeatTime += dt;
|
||||
while (_repeatInterval < _repeatTime) {
|
||||
_repeatTime -= _repeatInterval;
|
||||
fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
|
||||
} // of repeat iteration
|
||||
}
|
||||
}
|
||||
|
||||
bool hover( const osg::Vec2d& windowPos, const Info& ) override
|
||||
|
||||
@@ -35,12 +35,13 @@
|
||||
#include <osgDB/FileNameUtils>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "modellib.hxx"
|
||||
#include "SGReaderWriterXML.hxx"
|
||||
@@ -81,6 +82,7 @@ SGReaderWriterXML::readNode(const std::string& name,
|
||||
const osgDB::Options* options) const
|
||||
{
|
||||
std::string fileName = osgDB::findDataFile(name, options);
|
||||
simgear::ErrorReportContext ec{"model-xml", fileName};
|
||||
|
||||
osg::Node *result=0;
|
||||
try {
|
||||
@@ -262,8 +264,10 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
SGPropertyNode *overlay)
|
||||
{
|
||||
if (!path.exists()) {
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "Failed to load file: \"" << path << "\"");
|
||||
return std::make_tuple(0, (osg::Node *) NULL);
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLModelLoad,
|
||||
"Failed to load model XML: not found", path);
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "Failed to load file: \"" << path << "\"");
|
||||
return std::make_tuple(0, (osg::Node*)NULL);
|
||||
}
|
||||
|
||||
osg::ref_ptr<SGReaderWriterOptions> options;
|
||||
@@ -291,6 +295,8 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
try {
|
||||
readProperties(modelpath, props);
|
||||
} catch (const sg_exception &t) {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::XMLModelLoad,
|
||||
"Failed to load model XML:" + t.getFormattedMessage(), t.getLocation());
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "Failed to load xml: "
|
||||
<< t.getFormattedMessage());
|
||||
throw;
|
||||
@@ -305,18 +311,24 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
if (props->hasValue("/path")) {
|
||||
string modelPathStr = props->getStringValue("/path");
|
||||
modelpath = SGModelLib::findDataFile(modelPathStr, NULL, modelDir);
|
||||
if (modelpath.isNull())
|
||||
if (modelpath.isNull()) {
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::ThreeDModelLoad,
|
||||
"Model not found:" + modelPathStr, path);
|
||||
throw sg_io_exception("Model file not found: '" + modelPathStr + "'",
|
||||
path);
|
||||
path, {}, false);
|
||||
}
|
||||
|
||||
if (props->hasValue("/texture-path")) {
|
||||
string texturePathStr = props->getStringValue("/texture-path");
|
||||
if (!texturePathStr.empty())
|
||||
{
|
||||
texturepath = SGModelLib::findDataFile(texturePathStr, NULL, modelDir);
|
||||
if (texturepath.isNull())
|
||||
if (texturepath.isNull()) {
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::LoadingTexture,
|
||||
"Texture file not found:" + texturePathStr, path);
|
||||
throw sg_io_exception("Texture file not found: '" + texturePathStr + "'",
|
||||
path);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -343,9 +355,13 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
#else
|
||||
modelResult = osgDB::readRefNodeFile(modelpath.utf8Str(), options.get());
|
||||
#endif
|
||||
if (!modelResult.validNode())
|
||||
if (!modelResult.validNode()) {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::XMLModelLoad,
|
||||
"Failed to load 3D model:" + modelResult.message(), modelpath);
|
||||
throw sg_io_exception("Failed to load 3D model:" + modelResult.message(),
|
||||
modelpath);
|
||||
modelpath, {}, false);
|
||||
}
|
||||
|
||||
model = copyModel(modelResult.getNode());
|
||||
// Add an extra reference to the model stored in the database.
|
||||
// That is to avoid expiring the object from the cache even if
|
||||
@@ -413,6 +429,9 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
|
||||
if (submodelPath.isNull()) {
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "Failed to load file: \"" << subPathStr << "\"");
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLModelLoad,
|
||||
"Couldn't find file for submodel:" + subPathStr,
|
||||
SGPath::fromUtf8(subPathStr));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -425,6 +444,8 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
}
|
||||
}
|
||||
|
||||
simgear::ErrorReportContext("submodel", submodelPath.utf8Str());
|
||||
|
||||
try {
|
||||
int num_anims;
|
||||
std::tie(num_anims, submodel) = sgLoad3DModel_internal(submodelPath, options.get(),
|
||||
@@ -490,7 +511,8 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalParticleCallback::getEnabled()){//dbOptions->getPluginStringData("SimGear::PARTICLESYSTEM") != "OFF") {
|
||||
auto particlesManager = ParticlesGlobalManager::instance();
|
||||
if (particlesManager->isEnabled()) { //dbOptions->getPluginStringData("SimGear::PARTICLESYSTEM") != "OFF") {
|
||||
std::vector<SGPropertyNode_ptr> particle_nodes;
|
||||
particle_nodes = props->getChildren("particlesystem");
|
||||
for (unsigned i = 0; i < particle_nodes.size(); ++i) {
|
||||
@@ -503,9 +525,9 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
|
||||
options2->setDatabasePath(texturepath.utf8Str());
|
||||
}
|
||||
group->addChild(Particles::appendParticles(particle_nodes[i],
|
||||
prop_root,
|
||||
options2.get()));
|
||||
group->addChild(particlesManager->appendParticles(particle_nodes[i],
|
||||
prop_root,
|
||||
options2.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,13 +574,22 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
} // of object-names in the animation
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* Setup the model data for the node currently being animated.
|
||||
*/
|
||||
modelData.LoadAnimationValuesForElement(animation_nodes[i], i);
|
||||
|
||||
try {
|
||||
/*
|
||||
* Setup the model data for the node currently being animated.
|
||||
*/
|
||||
modelData.LoadAnimationValuesForElement(animation_nodes[i], i);
|
||||
|
||||
/// OSGFIXME: duh, why not only model?????
|
||||
SGAnimation::animate(modelData);
|
||||
/// OSGFIXME: duh, why not only model?????
|
||||
SGAnimation::animate(modelData);
|
||||
} catch (sg_exception& e) {
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLModelLoad,
|
||||
"Couldn't load animation " + animation_nodes[i]->getNameString()
|
||||
+ ":" + e.getFormattedMessage(),
|
||||
modelpath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
animationcount += animation_nodes.size();
|
||||
|
||||
@@ -80,7 +80,7 @@ void SGText::UpdateCallback::operator()(osg::Node * node, osg::NodeVisitor *nv )
|
||||
// be lazy and set the text only if the property has changed.
|
||||
// update() computes the glyph representation which looks
|
||||
// more expensive than a the above string compare.
|
||||
text->setText( buf );
|
||||
text->setText( buf, osgText::String::ENCODING_UTF8 );
|
||||
text->update();
|
||||
}
|
||||
traverse( node, nv );
|
||||
|
||||
@@ -39,9 +39,11 @@
|
||||
#include <simgear/bvh/BVHGroup.hxx>
|
||||
#include <simgear/bvh/BVHLineGeometry.hxx>
|
||||
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/math/interpolater.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/EffectCullVisitor.hxx>
|
||||
#include <simgear/scene/util/DeletionManager.hxx>
|
||||
@@ -773,10 +775,16 @@ bool SGAnimation::setCenterAndAxisFromObject(osg::Node *rootNode, SGVec3d& cente
|
||||
object_group->setNodeMask(0);
|
||||
}
|
||||
else {
|
||||
reportFailure(LoadFailure::Misconfigured, ErrorCode::XMLModelLoad,
|
||||
"Could not find valid line segment for axis animation:" + axis_object_name,
|
||||
SGPath::fromUtf8(_modelData.getPath()));
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "Could not find a valid line segment for animation: " << axis_object_name << " in file: " << _modelData.getPath());
|
||||
}
|
||||
}
|
||||
else if (can_warn) {
|
||||
reportFailure(LoadFailure::Misconfigured, ErrorCode::XMLModelLoad,
|
||||
"Could not find object for axis animation:" + axis_object_name,
|
||||
SGPath::fromUtf8(_modelData.getPath()));
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "Could not find at least one of the following objects for axis animation: " << axis_object_name << " in file: " << _modelData.getPath());
|
||||
}
|
||||
}
|
||||
@@ -1135,16 +1143,8 @@ void SpinAnimCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
double intPart;
|
||||
double rot = modf(rotation, &intPart);
|
||||
double angle = rot * 2.0 * osg::PI;
|
||||
const SGVec3d& sgcenter = transform->getCenter();
|
||||
const SGVec3d& sgaxis = transform->getAxis();
|
||||
Matrixd mat = Matrixd::translate(-sgcenter[0], -sgcenter[1], -sgcenter[2])
|
||||
* Matrixd::rotate(angle, sgaxis[0], sgaxis[1], sgaxis[2])
|
||||
* Matrixd::translate(sgcenter[0], sgcenter[1], sgcenter[2])
|
||||
* *cv->getModelViewMatrix();
|
||||
ref_ptr<RefMatrix> refmat = new RefMatrix(mat);
|
||||
cv->pushModelViewMatrix(refmat.get(), transform->getReferenceFrame());
|
||||
transform->setAngleRad(angle);
|
||||
traverse(transform, nv);
|
||||
cv->popModelViewMatrix();
|
||||
} else {
|
||||
traverse(transform, nv);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,12 @@
|
||||
#include <simgear/scene/util/SplicingVisitor.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/structure/Singleton.hxx>
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/structure/Singleton.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "model.hxx"
|
||||
|
||||
@@ -380,6 +381,8 @@ ref_ptr<Node> instantiateMaterialEffects(osg::Node* modelGroup,
|
||||
} else {
|
||||
effect = DefaultEffect::instance()->getEffect();
|
||||
SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get effect for " << options->getMaterialName());
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::LoadEffectsShaders,
|
||||
"Unable to get effect for material:" + options->getMaterialName());
|
||||
}
|
||||
} else {
|
||||
effect = DefaultEffect::instance()->getEffect();
|
||||
|
||||
@@ -29,12 +29,13 @@
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/misc/ResourceManager.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/scene/model/model.hxx>
|
||||
#include <simgear/scene/model/ModelRegistry.hxx>
|
||||
#include <simgear/scene/model/model.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/misc/ResourceManager.hxx>
|
||||
|
||||
#include "SGReaderWriterXML.hxx"
|
||||
|
||||
@@ -159,7 +160,6 @@ SGModelLib::loadDeferredModel(const string &path, SGPropertyNode *prop_root,
|
||||
proxyNode->setLoadingExternalReferenceMode(osg::ProxyNode::DEFER_LOADING_TO_DATABASE_PAGER);
|
||||
proxyNode->setFileName(0, path);
|
||||
|
||||
|
||||
osg::ref_ptr<SGReaderWriterOptions> opt;
|
||||
opt = SGReaderWriterOptions::copyOrCreate(osgDB::Registry::instance()->getOptions());
|
||||
opt->getDatabasePathList().push_front( osgDB::getFilePath(path) );
|
||||
|
||||
@@ -18,12 +18,9 @@
|
||||
#ifndef _SG_MODEL_LIB_HXX
|
||||
#define _SG_MODEL_LIB_HXX 1
|
||||
|
||||
#ifndef __cplusplus
|
||||
# error This library requires C++
|
||||
#endif
|
||||
|
||||
#include <simgear/compiler.h> // for SG_USING_STD
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <osg/Node>
|
||||
@@ -112,6 +109,10 @@ public:
|
||||
virtual void modelLoaded(const std::string& path, SGPropertyNode *prop,
|
||||
osg::Node* branch) = 0;
|
||||
virtual SGModelData* clone() const = 0;
|
||||
|
||||
using ErrorContext = std::map<std::string, std::string>;
|
||||
|
||||
virtual ErrorContext getErrorContext() const = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "particles.hxx"
|
||||
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
@@ -33,62 +36,306 @@
|
||||
#include <osgParticle/MultiSegmentPlacer>
|
||||
#include <osgParticle/SectorPlacer>
|
||||
#include <osgParticle/ConstantRateCounter>
|
||||
#include <osgParticle/ParticleSystemUpdater>
|
||||
#include <osgParticle/ParticleSystem>
|
||||
#include <osgParticle/FluidProgram>
|
||||
|
||||
#include <osgUtil/CullVisitor>
|
||||
#include <osg/Geode>
|
||||
#include <osg/Group>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Node>
|
||||
|
||||
#include "particles.hxx"
|
||||
|
||||
#include <simgear/scene/model/animation.hxx>
|
||||
|
||||
using ParticleSystemRef = osg::ref_ptr<osgParticle::ParticleSystem>;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
void GlobalParticleCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
enabled = !enabledNode || enabledNode->getBoolValue();
|
||||
if (!enabled)
|
||||
return;
|
||||
SGQuatd q
|
||||
= SGQuatd::fromLonLatDeg(modelRoot->getFloatValue("/position/longitude-deg",0),
|
||||
modelRoot->getFloatValue("/position/latitude-deg",0));
|
||||
osg::Matrix om(toOsg(q));
|
||||
osg::Vec3 v(0,0,9.81);
|
||||
gravity = om.preMult(v);
|
||||
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
|
||||
const osg::Vec3& zUpWind = Particles::getWindVector();
|
||||
osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
|
||||
wind = om.preMult(w);
|
||||
|
||||
// SG_LOG(SG_PARTICLES, SG_ALERT,
|
||||
// "wind vector:" << w[0] << "," <<w[1] << "," << w[2]);
|
||||
class ParticlesGlobalManager::ParticlesGlobalManagerPrivate : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
ParticlesGlobalManagerPrivate();
|
||||
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
|
||||
|
||||
// only call this with the lock held!
|
||||
osg::Group* internalGetCommonRoot();
|
||||
|
||||
void updateParticleSystemsFromCullCallback(int currentFrameNumber, osg::NodeVisitor* nv);
|
||||
|
||||
void addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame);
|
||||
|
||||
void registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps);
|
||||
void registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame);
|
||||
|
||||
std::mutex _lock;
|
||||
bool _frozen = false;
|
||||
double _simulationDt = 0.0;
|
||||
osg::ref_ptr<osg::Group> _commonRoot;
|
||||
osg::ref_ptr<osg::Geode> _commonGeode;
|
||||
osg::Vec3 _wind;
|
||||
bool _enabled = true;
|
||||
osg::Vec3 _gravity;
|
||||
// osg::Vec3 _localWind;
|
||||
SGGeod _currentPosition;
|
||||
|
||||
SGConstPropertyNode_ptr _enabledNode;
|
||||
|
||||
using ParticleSystemWeakRef = osg::observer_ptr<osgParticle::ParticleSystem>;
|
||||
using ParticleSystemsWeakRefVec = std::vector<ParticleSystemWeakRef>;
|
||||
|
||||
using ParticleSystemsStrongRefVec = std::vector<ParticleSystemRef>;
|
||||
|
||||
ParticleSystemsWeakRefVec _systems;
|
||||
osg::ref_ptr<ParticlesGlobalManager::UpdaterCallback> _cullCallback;
|
||||
|
||||
using GroupRefVec = std::vector<osg::ref_ptr<osg::Group>>;
|
||||
GroupRefVec _newWorldParticles;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief this class replaces the need to use osgParticle::ParticleSystemUpdater, which has some
|
||||
thread-safety and ownership complications in our us case
|
||||
*/
|
||||
class ParticlesGlobalManager::UpdaterCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
UpdaterCallback(ParticlesGlobalManagerPrivate* p) : _manager(p)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
|
||||
{
|
||||
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
|
||||
if (cv && nv->getFrameStamp()) {
|
||||
if (_frameNumber < nv->getFrameStamp()->getFrameNumber()) {
|
||||
_frameNumber = nv->getFrameStamp()->getFrameNumber();
|
||||
_manager->updateParticleSystemsFromCullCallback(_frameNumber, nv);
|
||||
}
|
||||
}
|
||||
|
||||
// note, callback is responsible for scenegraph traversal so
|
||||
// they must call traverse(node,nv) to ensure that the
|
||||
// scene graph subtree (and associated callbacks) are traversed.
|
||||
traverse(node, nv);
|
||||
}
|
||||
|
||||
unsigned int _frameNumber = 0;
|
||||
ParticlesGlobalManagerPrivate* _manager;
|
||||
};
|
||||
|
||||
/**
|
||||
single-shot node callback, used to register a particle system with the global manager. Once run, removes
|
||||
itself. We used this to avoid updating a particle system until the load is complete and merged into the
|
||||
main scene.
|
||||
*/
|
||||
class ParticlesGlobalManager::RegistrationCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
|
||||
{
|
||||
auto d = ParticlesGlobalManager::instance()->d;
|
||||
d->addParticleSystem(_system, _frame);
|
||||
node->removeUpdateCallback(this); // suicide
|
||||
}
|
||||
|
||||
ParticleSystemRef _system;
|
||||
osg::ref_ptr<osg::Group> _frame;
|
||||
};
|
||||
|
||||
ParticlesGlobalManager::ParticlesGlobalManagerPrivate::ParticlesGlobalManagerPrivate() : _commonGeode(new osg::Geode),
|
||||
_cullCallback(new UpdaterCallback(this))
|
||||
{
|
||||
// callbacks are registered in initFromMainThread : depending on timing,
|
||||
// this constructor might be called from an osgDB thread
|
||||
}
|
||||
|
||||
|
||||
//static members
|
||||
osg::Vec3 GlobalParticleCallback::gravity;
|
||||
osg::Vec3 GlobalParticleCallback::wind;
|
||||
bool GlobalParticleCallback::enabled = true;
|
||||
SGConstPropertyNode_ptr GlobalParticleCallback::enabledNode = 0;
|
||||
|
||||
osg::ref_ptr<osg::Group> Particles::commonRoot;
|
||||
osg::ref_ptr<osgParticle::ParticleSystemUpdater> Particles::psu = new osgParticle::ParticleSystemUpdater;
|
||||
osg::ref_ptr<osg::Geode> Particles::commonGeode = new osg::Geode;
|
||||
osg::Vec3 Particles::_wind;
|
||||
bool Particles::_frozen = false;
|
||||
|
||||
Particles::Particles() :
|
||||
useGravity(false),
|
||||
useWind(false)
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
_systems.push_back(ps);
|
||||
|
||||
// we're inside an update callback here, so better not modify the
|
||||
// scene structure; defer it to later
|
||||
if (frame.get()) {
|
||||
_newWorldParticles.push_back(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps)
|
||||
{
|
||||
auto cb = new RegistrationCallback;
|
||||
cb->_system = ps;
|
||||
node->addUpdateCallback(cb);
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame)
|
||||
{
|
||||
auto cb = new RegistrationCallback;
|
||||
cb->_system = ps;
|
||||
cb->_frame = frame;
|
||||
node->addUpdateCallback(cb);
|
||||
}
|
||||
|
||||
// this is called from the main thread during scenery init
|
||||
// after this, we beign updarting particle systems
|
||||
void ParticlesGlobalManager::initFromMainThread()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->internalGetCommonRoot();
|
||||
d->_commonRoot->addUpdateCallback(d.get());
|
||||
d->_commonRoot->setCullingActive(false);
|
||||
d->_commonRoot->addCullCallback(d->_cullCallback);
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::update(double dt, const SGGeod& pos)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_simulationDt = dt;
|
||||
d->_currentPosition = pos;
|
||||
|
||||
for (auto f : d->_newWorldParticles) {
|
||||
d->internalGetCommonRoot()->addChild(f);
|
||||
}
|
||||
|
||||
d->_newWorldParticles.clear();
|
||||
}
|
||||
|
||||
// this is called from the main thread, since it's an update callback
|
||||
// lock any state used by updateParticleSystemsFromCullCallback, which
|
||||
// runs during culling, potentialy on a different thread
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
_enabled = !_enabledNode || _enabledNode->getBoolValue();
|
||||
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
const auto q = SGQuatd::fromLonLat(_currentPosition);
|
||||
osg::Matrix om(toOsg(q));
|
||||
osg::Vec3 v(0, 0, 9.81);
|
||||
_gravity = om.preMult(v);
|
||||
|
||||
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
|
||||
// const osg::Vec3& zUpWind = _wind;
|
||||
// osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
|
||||
// _localWind = om.preMult(w);
|
||||
|
||||
// while we have the lock, remove all expired systems
|
||||
auto firstInvalid = std::remove_if(_systems.begin(), _systems.end(), [](const ParticleSystemWeakRef& weak) {
|
||||
return !weak.valid();
|
||||
});
|
||||
_systems.erase(firstInvalid, _systems.end());
|
||||
}
|
||||
|
||||
// only call this with the lock held!
|
||||
osg::Group* ParticlesGlobalManager::ParticlesGlobalManagerPrivate::internalGetCommonRoot()
|
||||
{
|
||||
if (!_commonRoot.valid()) {
|
||||
_commonRoot = new osg::Group;
|
||||
_commonRoot->setName("common particle system root");
|
||||
_commonGeode->setName("common particle system geode");
|
||||
_commonRoot->addChild(_commonGeode);
|
||||
_commonRoot->setNodeMask(~simgear::MODELLIGHT_BIT);
|
||||
}
|
||||
return _commonRoot.get();
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::updateParticleSystemsFromCullCallback(int frameNumber, osg::NodeVisitor* nv)
|
||||
{
|
||||
ParticleSystemsStrongRefVec activeSystems;
|
||||
double dt = 0.0;
|
||||
|
||||
// begin locked section
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
activeSystems.reserve(_systems.size());
|
||||
for (const auto& psref : _systems) {
|
||||
ParticleSystemRef owningRef;
|
||||
if (!psref.lock(owningRef)) {
|
||||
// pointed to system is gone, skip it
|
||||
// we will clean these up in the update callback, don't
|
||||
// worry about that here
|
||||
continue;
|
||||
}
|
||||
|
||||
// add to the list we will update now
|
||||
activeSystems.push_back(owningRef);
|
||||
}
|
||||
|
||||
dt = _simulationDt;
|
||||
} // of locked section
|
||||
|
||||
// from here on, don't access class data; copy it all to local variables
|
||||
// before this line. this is important so we're not holding _lock during
|
||||
// culling, which might block osgDB threads for example.
|
||||
|
||||
if (dt <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& ps : activeSystems) {
|
||||
// code inside here is copied from osgParticle::ParticleSystemUpdate
|
||||
osgParticle::ParticleSystem::ScopedWriteLock lock(*(ps->getReadWriteMutex()));
|
||||
|
||||
// We need to allow at least 2 frames difference, because the particle system's lastFrameNumber
|
||||
// is updated in the draw thread which may not have completed yet.
|
||||
if (!ps->isFrozen() &&
|
||||
(!ps->getFreezeOnCull() || ((frameNumber - ps->getLastFrameNumber()) <= 2))) {
|
||||
ps->update(dt, *nv);
|
||||
}
|
||||
// end of code copied from osgParticle::ParticleSystemUpdate
|
||||
}
|
||||
}
|
||||
|
||||
static std::mutex static_managerLock;
|
||||
static std::unique_ptr<ParticlesGlobalManager> static_instance;
|
||||
|
||||
ParticlesGlobalManager* ParticlesGlobalManager::instance()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(static_managerLock);
|
||||
if (!static_instance) {
|
||||
static_instance.reset(new ParticlesGlobalManager);
|
||||
}
|
||||
|
||||
return static_instance.get();
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(static_managerLock);
|
||||
static_instance.reset();
|
||||
}
|
||||
|
||||
ParticlesGlobalManager::ParticlesGlobalManager() : d(new ParticlesGlobalManagerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
bool ParticlesGlobalManager::isEnabled() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->_enabled;
|
||||
}
|
||||
|
||||
bool ParticlesGlobalManager::isFrozen() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->_frozen;
|
||||
}
|
||||
|
||||
osg::Vec3 ParticlesGlobalManager::getWindVector() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->_wind;
|
||||
}
|
||||
|
||||
template <typename Object>
|
||||
class PointerGuard{
|
||||
public:
|
||||
PointerGuard() : _ptr(0) {}
|
||||
Object* get() { return _ptr; }
|
||||
Object* operator () ()
|
||||
{
|
||||
@@ -97,24 +344,9 @@ public:
|
||||
return _ptr;
|
||||
}
|
||||
private:
|
||||
Object* _ptr;
|
||||
Object* _ptr = nullptr;
|
||||
};
|
||||
|
||||
osg::Group* Particles::getCommonRoot()
|
||||
{
|
||||
if(!commonRoot.valid())
|
||||
{
|
||||
SG_LOG(SG_PARTICLES, SG_DEBUG, "Particle common root called.");
|
||||
commonRoot = new osg::Group;
|
||||
commonRoot.get()->setName("common particle system root");
|
||||
commonGeode.get()->setName("common particle system geode");
|
||||
commonRoot.get()->addChild(commonGeode.get());
|
||||
commonRoot.get()->addChild(psu.get());
|
||||
commonRoot->setNodeMask( ~simgear::MODELLIGHT_BIT );
|
||||
}
|
||||
return commonRoot.get();
|
||||
}
|
||||
|
||||
void transformParticles(osgParticle::ParticleSystem* particleSys,
|
||||
const osg::Matrix& mat)
|
||||
{
|
||||
@@ -129,10 +361,182 @@ void transformParticles(osgParticle::ParticleSystem* particleSys,
|
||||
}
|
||||
}
|
||||
|
||||
osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot,
|
||||
const osgDB::Options*
|
||||
options)
|
||||
void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
auto globalManager = ParticlesGlobalManager::instance();
|
||||
|
||||
//SG_LOG(SG_PARTICLES, SG_ALERT, "callback!\n");
|
||||
particleSys->setFrozen(globalManager->isFrozen());
|
||||
|
||||
using namespace osg;
|
||||
if (shooterValue)
|
||||
shooter->setInitialSpeedRange(shooterValue->getValue(),
|
||||
(shooterValue->getValue() + shooterExtraRange));
|
||||
if (counterValue)
|
||||
counter->setRateRange(counterValue->getValue(),
|
||||
counterValue->getValue() + counterExtraRange);
|
||||
else if (counterCond)
|
||||
counter->setRateRange(counterStaticValue,
|
||||
counterStaticValue + counterStaticExtraRange);
|
||||
if (!globalManager->isEnabled() || (counterCond && !counterCond->test()))
|
||||
counter->setRateRange(0, 0);
|
||||
bool colorchange = false;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (colorComponents[i]) {
|
||||
staticColorComponents[i] = colorComponents[i]->getValue();
|
||||
colorchange = true;
|
||||
}
|
||||
}
|
||||
if (colorchange)
|
||||
particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
|
||||
if (startSizeValue)
|
||||
startSize = startSizeValue->getValue();
|
||||
if (endSizeValue)
|
||||
endSize = endSizeValue->getValue();
|
||||
if (startSizeValue || endSizeValue)
|
||||
particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
|
||||
if (lifeValue)
|
||||
particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
|
||||
|
||||
if (particleFrame.valid()) {
|
||||
MatrixList mlist = node->getWorldMatrices();
|
||||
if (!mlist.empty()) {
|
||||
const Matrix& particleMat = particleFrame->getMatrix();
|
||||
Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
|
||||
Vec3d displace = emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
|
||||
particleMat(3, 2));
|
||||
if (displace * displace > 10000.0 * 10000.0) {
|
||||
// Make new frame for particle system, coincident with
|
||||
// the emitter frame, but oriented with local Z.
|
||||
SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
|
||||
Matrix newParticleMat = makeZUpFrame(geod);
|
||||
Matrix changeParticleFrame = particleMat * Matrix::inverse(newParticleMat);
|
||||
particleFrame->setMatrix(newParticleMat);
|
||||
transformParticles(particleSys.get(), changeParticleFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (program.valid() && useWind)
|
||||
program->setWind(globalManager->getWindVector());
|
||||
}
|
||||
|
||||
void Particles::setupShooterSpeedData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
shooterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!shooterValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: shooter property error!\n");
|
||||
}
|
||||
shooterExtraRange = configNode->getFloatValue("extrarange", 0);
|
||||
}
|
||||
|
||||
void Particles::setupCounterData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!counterValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "counter property error!\n");
|
||||
}
|
||||
counterExtraRange = configNode->getFloatValue("extrarange", 0);
|
||||
}
|
||||
|
||||
void Particles::Particles::setupCounterCondition(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterCond = sgReadCondition(modelRoot, configNode);
|
||||
}
|
||||
|
||||
void Particles::setupCounterCondition(float aCounterStaticValue,
|
||||
float aCounterStaticExtraRange)
|
||||
{
|
||||
counterStaticValue = aCounterStaticValue;
|
||||
counterStaticExtraRange = aCounterStaticExtraRange;
|
||||
}
|
||||
|
||||
void Particles::setupStartSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
startSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!startSizeValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Particles::setupEndSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
endSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!endSizeValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Particles::setupLifeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
lifeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!lifeValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: lifeValue error!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Particles::setupColorComponent(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot, int color,
|
||||
int component)
|
||||
{
|
||||
SGSharedPtr<SGExpressiond> colorValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(),
|
||||
SGLimitsd::max());
|
||||
if (!colorValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: color property error!\n");
|
||||
}
|
||||
colorComponents[(color * 4) + component] = colorValue;
|
||||
//number of color components = 4
|
||||
}
|
||||
|
||||
void Particles::setupStaticColorComponent(float r1, float g1, float b1, float a1,
|
||||
float r2, float g2, float b2, float a2)
|
||||
{
|
||||
staticColorComponents[0] = r1;
|
||||
staticColorComponents[1] = g1;
|
||||
staticColorComponents[2] = b1;
|
||||
staticColorComponents[3] = a1;
|
||||
staticColorComponents[4] = r2;
|
||||
staticColorComponents[5] = g2;
|
||||
staticColorComponents[6] = b2;
|
||||
staticColorComponents[7] = a2;
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::setWindVector(const osg::Vec3& wind)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_wind = wind;
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::setWindFrom(const double from_deg, const double speed_kt)
|
||||
{
|
||||
double map_rad = -from_deg * SG_DEGREES_TO_RADIANS;
|
||||
double speed_mps = speed_kt * SG_KT_TO_MPS;
|
||||
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_wind[0] = cos(map_rad) * speed_mps;
|
||||
d->_wind[1] = sin(map_rad) * speed_mps;
|
||||
d->_wind[2] = 0.0;
|
||||
}
|
||||
|
||||
|
||||
osg::Group* ParticlesGlobalManager::getCommonRoot()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->internalGetCommonRoot();
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Group> ParticlesGlobalManager::appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options)
|
||||
{
|
||||
SG_LOG(SG_PARTICLES, SG_DEBUG,
|
||||
"Setting up a particle system." << std::boolalpha
|
||||
@@ -152,7 +556,7 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
<< "\n Wind: " << configNode->getChild("program")->getBoolValue("wind", true)
|
||||
<< std::noboolalpha);
|
||||
|
||||
osgParticle::ParticleSystem *particleSys;
|
||||
osg::ref_ptr<osgParticle::ParticleSystem> particleSys;
|
||||
|
||||
//create a generic particle system
|
||||
std::string type = configNode->getStringValue("type", "normal");
|
||||
@@ -160,11 +564,10 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
particleSys = new osgParticle::ParticleSystem;
|
||||
else
|
||||
particleSys = new osgParticle::ConnectedParticleSystem;
|
||||
|
||||
//may not be used depending on the configuration
|
||||
PointerGuard<Particles> callback;
|
||||
|
||||
getPSU()->addParticleSystem(particleSys);
|
||||
getPSU()->setUpdateCallback(new GlobalParticleCallback(modelRoot));
|
||||
//contains counter, placer and shooter by default
|
||||
osgParticle::ModularEmitter* emitter = new osgParticle::ModularEmitter;
|
||||
|
||||
@@ -172,7 +575,7 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
|
||||
// Set up the alignment node ("stolen" from animation.cxx)
|
||||
// XXX Order of rotations is probably not correct.
|
||||
osg::MatrixTransform *align = new osg::MatrixTransform;
|
||||
osg::ref_ptr<osg::MatrixTransform> align = new osg::MatrixTransform;
|
||||
osg::Matrix res_matrix;
|
||||
res_matrix.makeRotate(
|
||||
configNode->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
|
||||
@@ -187,12 +590,8 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
configNode->getFloatValue("offsets/y-m", 0.0),
|
||||
configNode->getFloatValue("offsets/z-m", 0.0));
|
||||
align->setMatrix(res_matrix * tmat);
|
||||
|
||||
align->setName("particle align");
|
||||
|
||||
//if (dynamic_cast<CustomModularEmitter*>(emitter)==0) SG_LOG(SG_PARTICLES, SG_ALERT, "observer error\n");
|
||||
//align->addObserver(dynamic_cast<CustomModularEmitter*>(emitter));
|
||||
|
||||
align->addChild(emitter);
|
||||
|
||||
//this name can be used in the XML animation as if it was a submodel
|
||||
@@ -209,7 +608,6 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
osg::Geode* g = new osg::Geode;
|
||||
g->addDrawable(particleSys);
|
||||
callback()->particleFrame->addChild(g);
|
||||
getCommonRoot()->addChild(callback()->particleFrame.get());
|
||||
}
|
||||
std::string textureFile;
|
||||
if (configNode->hasValue("texture")) {
|
||||
@@ -505,66 +903,25 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
emitter->setUpdateCallback(callback.get());
|
||||
}
|
||||
|
||||
if (attach == "local") {
|
||||
d->registerNewLocalParticleSystem(align, particleSys);
|
||||
} else {
|
||||
d->registerNewWorldParticleSystem(align, particleSys, callback()->particleFrame);
|
||||
}
|
||||
|
||||
return align;
|
||||
}
|
||||
|
||||
void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
void ParticlesGlobalManager::setSwitchNode(const SGPropertyNode* n)
|
||||
{
|
||||
//SG_LOG(SG_PARTICLES, SG_ALERT, "callback!\n");
|
||||
this->particleSys->setFrozen(_frozen);
|
||||
|
||||
using namespace osg;
|
||||
if (shooterValue)
|
||||
shooter->setInitialSpeedRange(shooterValue->getValue(),
|
||||
(shooterValue->getValue()
|
||||
+ shooterExtraRange));
|
||||
if (counterValue)
|
||||
counter->setRateRange(counterValue->getValue(),
|
||||
counterValue->getValue() + counterExtraRange);
|
||||
else if (counterCond)
|
||||
counter->setRateRange(counterStaticValue,
|
||||
counterStaticValue + counterStaticExtraRange);
|
||||
if (!GlobalParticleCallback::getEnabled() || (counterCond && !counterCond->test()))
|
||||
counter->setRateRange(0, 0);
|
||||
bool colorchange=false;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (colorComponents[i]) {
|
||||
staticColorComponents[i] = colorComponents[i]->getValue();
|
||||
colorchange=true;
|
||||
}
|
||||
}
|
||||
if (colorchange)
|
||||
particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4( Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
|
||||
if (startSizeValue)
|
||||
startSize = startSizeValue->getValue();
|
||||
if (endSizeValue)
|
||||
endSize = endSizeValue->getValue();
|
||||
if (startSizeValue || endSizeValue)
|
||||
particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
|
||||
if (lifeValue)
|
||||
particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
|
||||
|
||||
if (particleFrame.valid()) {
|
||||
MatrixList mlist = node->getWorldMatrices();
|
||||
if (!mlist.empty()) {
|
||||
const Matrix& particleMat = particleFrame->getMatrix();
|
||||
Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
|
||||
Vec3d displace
|
||||
= emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
|
||||
particleMat(3, 2));
|
||||
if (displace * displace > 10000.0 * 10000.0) {
|
||||
// Make new frame for particle system, coincident with
|
||||
// the emitter frame, but oriented with local Z.
|
||||
SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
|
||||
Matrix newParticleMat = makeZUpFrame(geod);
|
||||
Matrix changeParticleFrame
|
||||
= particleMat * Matrix::inverse(newParticleMat);
|
||||
particleFrame->setMatrix(newParticleMat);
|
||||
transformParticles(particleSys.get(), changeParticleFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (program.valid() && useWind)
|
||||
program->setWind(_wind);
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_enabledNode = n;
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::setFrozen(bool b)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_frozen = b;
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// particles.hxx - classes to manage particles
|
||||
// Copyright (C) 2008 Tiago Gusm<73>o
|
||||
// Copyright (C) 2008 Tiago Gusm<73>o
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
@@ -41,7 +41,6 @@ class NodeVisitor;
|
||||
namespace osgParticle
|
||||
{
|
||||
class ParticleSystem;
|
||||
class ParticleSystemUpdater;
|
||||
}
|
||||
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
@@ -53,133 +52,37 @@ class ParticleSystemUpdater;
|
||||
#include <simgear/math/SGMatrix.hxx>
|
||||
|
||||
|
||||
// Has anyone done anything *really* stupid, like making min and max macros?
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#include "animation.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class GlobalParticleCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
GlobalParticleCallback(const SGPropertyNode* modelRoot)
|
||||
{
|
||||
this->modelRoot=modelRoot;
|
||||
}
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||
|
||||
static const osg::Vec3 &getGravityVector()
|
||||
{
|
||||
return gravity;
|
||||
}
|
||||
|
||||
static const osg::Vec3 &getWindVector()
|
||||
{
|
||||
return wind;
|
||||
}
|
||||
|
||||
static void setSwitch(const SGPropertyNode* n)
|
||||
{
|
||||
enabledNode = n;
|
||||
}
|
||||
|
||||
static bool getEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static osg::Vec3 gravity;
|
||||
static osg::Vec3 wind;
|
||||
SGConstPropertyNode_ptr modelRoot;
|
||||
static SGConstPropertyNode_ptr enabledNode;
|
||||
static bool enabled;
|
||||
};
|
||||
|
||||
|
||||
class ParticlesGlobalManager;
|
||||
|
||||
class Particles : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
Particles();
|
||||
Particles() = default;
|
||||
|
||||
static osg::Group * appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
|
||||
|
||||
void setupShooterSpeedData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
shooterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!shooterValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "shooter property error!\n");
|
||||
}
|
||||
shooterExtraRange = configNode->getFloatValue("extrarange",0);
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupCounterData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!counterValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "counter property error!\n");
|
||||
}
|
||||
counterExtraRange = configNode->getFloatValue("extrarange",0);
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupCounterCondition(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterCond = sgReadCondition(modelRoot, configNode);
|
||||
}
|
||||
|
||||
SGPropertyNode* modelRoot);
|
||||
void setupCounterCondition(float counterStaticValue,
|
||||
float counterStaticExtraRange)
|
||||
{
|
||||
this->counterStaticValue = counterStaticValue;
|
||||
this->counterStaticExtraRange = counterStaticExtraRange;
|
||||
}
|
||||
float counterStaticExtraRange);
|
||||
|
||||
void setupStartSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
startSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!startSizeValue)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupEndSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
endSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!endSizeValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupLifeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
lifeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!lifeValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "lifeValue error!\n");
|
||||
}
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupStaticSizeData(float startSize, float endSize)
|
||||
{
|
||||
@@ -212,60 +115,14 @@ public:
|
||||
//component: r=0, g=1, ... ; color: 0=start color, 1=end color
|
||||
void setupColorComponent(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot, int color,
|
||||
int component)
|
||||
{
|
||||
SGExpressiond *colorValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(),
|
||||
SGLimitsd::max());
|
||||
if(!colorValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "color property error!\n");
|
||||
}
|
||||
colorComponents[(color*4)+component] = colorValue;
|
||||
//number of color components = 4
|
||||
}
|
||||
int component);
|
||||
|
||||
void setupStaticColorComponent(float r1,float g1, float b1, float a1,
|
||||
float r2, float g2, float b2, float a2)
|
||||
{
|
||||
staticColorComponents[0] = r1;
|
||||
staticColorComponents[1] = g1;
|
||||
staticColorComponents[2] = b1;
|
||||
staticColorComponents[3] = a1;
|
||||
staticColorComponents[4] = r2;
|
||||
staticColorComponents[5] = g2;
|
||||
staticColorComponents[6] = b2;
|
||||
staticColorComponents[7] = a2;
|
||||
}
|
||||
void setupStaticColorComponent(float r1, float g1, float b1, float a1,
|
||||
float r2, float g2, float b2, float a2);
|
||||
|
||||
static osg::Group * getCommonRoot();
|
||||
|
||||
static osg::Geode * getCommonGeode()
|
||||
{
|
||||
return commonGeode.get();
|
||||
}
|
||||
|
||||
static osgParticle::ParticleSystemUpdater * getPSU()
|
||||
{
|
||||
return psu.get();
|
||||
}
|
||||
|
||||
static void setFrozen(bool e) { _frozen = e; }
|
||||
|
||||
/**
|
||||
* Set and get the wind vector for particles in the
|
||||
* atmosphere. This vector is in the Z-up Y-north frame, and the
|
||||
* magnitude is the velocity in meters per second.
|
||||
*/
|
||||
static void setWindVector(const osg::Vec3& wind) { _wind = wind; }
|
||||
static void setWindFrom(const double from_deg, const double speed_kt) {
|
||||
double map_rad = -from_deg * SG_DEGREES_TO_RADIANS;
|
||||
double speed_mps = speed_kt * SG_KT_TO_MPS;
|
||||
_wind[0] = cos(map_rad) * speed_mps;
|
||||
_wind[1] = sin(map_rad) * speed_mps;
|
||||
_wind[2] = 0.0;
|
||||
}
|
||||
static const osg::Vec3& getWindVector() { return _wind; }
|
||||
protected:
|
||||
friend class ParticlesGlobalManager;
|
||||
|
||||
float shooterExtraRange;
|
||||
float counterExtraRange;
|
||||
SGSharedPtr<SGExpressiond> shooterValue;
|
||||
@@ -285,39 +142,60 @@ protected:
|
||||
osg::ref_ptr<osgParticle::ParticleSystem> particleSys;
|
||||
osg::ref_ptr<osgParticle::FluidProgram> program;
|
||||
osg::ref_ptr<osg::MatrixTransform> particleFrame;
|
||||
|
||||
bool useGravity;
|
||||
bool useWind;
|
||||
static bool _frozen;
|
||||
static osg::ref_ptr<osgParticle::ParticleSystemUpdater> psu;
|
||||
static osg::ref_ptr<osg::Group> commonRoot;
|
||||
static osg::ref_ptr<osg::Geode> commonGeode;
|
||||
static osg::Vec3 _wind;
|
||||
|
||||
bool useGravity = false;
|
||||
bool useWind = false;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
class CustomModularEmitter : public osgParticle::ModularEmitter, public osg::Observer
|
||||
class ParticlesGlobalManager
|
||||
{
|
||||
public:
|
||||
~ParticlesGlobalManager() = default;
|
||||
|
||||
virtual void objectDeleted (void *)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "object deleted!\n");
|
||||
static ParticlesGlobalManager* instance();
|
||||
static void clear();
|
||||
|
||||
SGParticles::getCommonRoot()->removeChild(this->getParticleSystem()->getParent(0));
|
||||
SGParticles::getPSU()->removeParticleSystem(this->getParticleSystem());
|
||||
}
|
||||
bool isEnabled() const;
|
||||
|
||||
osg::ref_ptr<osg::Group> appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
|
||||
|
||||
// ~CustomModularEmitter()
|
||||
// {
|
||||
// SG_LOG(SG_GENERAL, SG_ALERT, "object deleted!\n");
|
||||
//
|
||||
// SGParticles::getCommonRoot()->removeChild(this->getParticleSystem()->getParent(0));
|
||||
// SGParticles::getPSU()->removeParticleSystem(this->getParticleSystem());
|
||||
// }
|
||||
osg::Group* getCommonRoot();
|
||||
osg::Geode* getCommonGeode();
|
||||
|
||||
void initFromMainThread();
|
||||
|
||||
void setFrozen(bool e);
|
||||
bool isFrozen() const;
|
||||
|
||||
/**
|
||||
@brief update function: call from the main thread , outside of OSG calls.
|
||||
*/
|
||||
void update(double dt, const SGGeod& pos);
|
||||
|
||||
void setSwitchNode(const SGPropertyNode* n);
|
||||
|
||||
/**
|
||||
* Set and get the wind vector for particles in the
|
||||
* atmosphere. This vector is in the Z-up Y-north frame, and the
|
||||
* magnitude is the velocity in meters per second.
|
||||
*/
|
||||
void setWindVector(const osg::Vec3& wind);
|
||||
void setWindFrom(const double from_deg, const double speed_kt);
|
||||
|
||||
osg::Vec3 getWindVector() const;
|
||||
|
||||
private:
|
||||
ParticlesGlobalManager();
|
||||
|
||||
class ParticlesGlobalManagerPrivate;
|
||||
class UpdaterCallback;
|
||||
class RegistrationCallback;
|
||||
|
||||
// because Private inherits NodeCallback, we need to own it
|
||||
// via an osg::ref_ptr
|
||||
osg::ref_ptr<ParticlesGlobalManagerPrivate> d;
|
||||
};
|
||||
*/
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user