Compare commits
68 Commits
version/20
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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)
|
||||
|
||||
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 +1 @@
|
||||
2020.3.1
|
||||
2020.3.9
|
||||
|
||||
@@ -168,7 +168,9 @@ target_link_libraries(SimGearCore PRIVATE
|
||||
${COCOA_LIBRARY}
|
||||
${CURL_LIBRARIES}
|
||||
${WINSOCK_LIBRARY}
|
||||
${SHLWAPI_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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -3,8 +3,12 @@ include (SimGearComponent)
|
||||
|
||||
set(HEADERS debug_types.h
|
||||
logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx
|
||||
LogCallback.hxx LogEntry.hxx)
|
||||
LogCallback.hxx LogEntry.hxx
|
||||
ErrorReportingCallback.hxx)
|
||||
|
||||
set(SOURCES logstream.cxx BufferedLogCallback.cxx
|
||||
LogCallback.cxx LogEntry.cxx)
|
||||
LogCallback.cxx LogEntry.cxx
|
||||
ErrorReportingCallback.cxx
|
||||
)
|
||||
|
||||
simgear_component(debug debug "${SOURCES}" "${HEADERS}")
|
||||
|
||||
47
simgear/debug/ErrorReportingCallback.cxx
Normal file
47
simgear/debug/ErrorReportingCallback.cxx
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace simgear {
|
||||
|
||||
static ErrorReportCallback static_callback;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
31
simgear/debug/ErrorReportingCallback.hxx
Normal file
31
simgear/debug/ErrorReportingCallback.hxx
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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 <string>
|
||||
|
||||
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);
|
||||
|
||||
} // namespace simgear
|
||||
@@ -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"
|
||||
@@ -646,16 +647,21 @@ bool SGMetar::scanWind()
|
||||
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;
|
||||
@@ -680,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;
|
||||
}
|
||||
|
||||
@@ -1053,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))
|
||||
|
||||
@@ -81,6 +81,15 @@ 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()
|
||||
|
||||
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
|
||||
@@ -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,22 +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?
|
||||
auto rawTxt = reinterpret_cast<char*>(result->dnstxt_txt[i].txt);
|
||||
if (!rawTxt) {
|
||||
continue;
|
||||
}
|
||||
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];
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -176,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,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();
|
||||
@@ -223,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -242,6 +266,7 @@ Client::Client() :
|
||||
|
||||
void Client::makeRequest(const Request_ptr& r)
|
||||
{
|
||||
d->_activeRequests.push_back(r);
|
||||
r->submit(this);
|
||||
}
|
||||
|
||||
@@ -252,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;
|
||||
|
||||
@@ -81,21 +81,8 @@ void Client::ClientPrivate::createCurlMulti() {
|
||||
#endif
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
d->tlsCertificatePath = SGPath::fromEnv("SIMGEAR_TLS_CERT_PATH");
|
||||
|
||||
static bool didInitCurlGlobal = false;
|
||||
static std::mutex initMutex;
|
||||
|
||||
@@ -105,7 +92,7 @@ Client::Client() :
|
||||
didInitCurlGlobal = true;
|
||||
}
|
||||
|
||||
d->createCurlMulti();
|
||||
reset();
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
@@ -139,8 +126,21 @@ void Client::setMaxPipelineDepth(unsigned int depth)
|
||||
|
||||
void Client::reset()
|
||||
{
|
||||
curl_multi_cleanup(d->curlMulti);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
|
||||
#include "HTTPRepository.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
@@ -81,6 +81,45 @@ std::string innerResultCodeAsString(HTTPRepository::ResultCode code) {
|
||||
return "Unknown response code";
|
||||
}
|
||||
|
||||
struct HashCacheEntry {
|
||||
std::string filePath;
|
||||
time_t modTime;
|
||||
size_t lengthBytes;
|
||||
std::string hashHex;
|
||||
};
|
||||
|
||||
using HashCache = std::unordered_map<std::string, HashCacheEntry>;
|
||||
|
||||
std::string computeHashForPath(const SGPath& p)
|
||||
{
|
||||
if (!p.exists())
|
||||
return {};
|
||||
|
||||
sha1nfo info;
|
||||
sha1_init(&info);
|
||||
|
||||
const int bufSize = 1024 * 1024;
|
||||
char* buf = static_cast<char*>(malloc(bufSize));
|
||||
if (!buf) {
|
||||
sg_io_exception("Couldn't allocate SHA1 computation buffer");
|
||||
}
|
||||
|
||||
size_t readLen;
|
||||
SGBinaryFile f(p);
|
||||
if (!f.open(SG_IO_IN)) {
|
||||
free(buf);
|
||||
throw sg_io_exception("Couldn't open file for compute hash", p);
|
||||
}
|
||||
while ((readLen = f.read(buf, bufSize)) > 0) {
|
||||
sha1_write(&info, buf, readLen);
|
||||
}
|
||||
|
||||
f.close();
|
||||
free(buf);
|
||||
std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH);
|
||||
return strutils::encodeHex(hashBytes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class HTTPDirectory
|
||||
@@ -104,13 +143,16 @@ class HTTPDirectory
|
||||
|
||||
HTTPRepository::EntryType type;
|
||||
std::string name, hash;
|
||||
size_t sizeInBytes = 0;
|
||||
SGPath path; // absolute path on disk
|
||||
size_t sizeInBytes = 0;
|
||||
SGPath path; // absolute path on disk
|
||||
};
|
||||
|
||||
typedef std::vector<ChildInfo> ChildInfoList;
|
||||
ChildInfoList children;
|
||||
|
||||
mutable HashCache hashes;
|
||||
mutable bool hashCacheDirty = false;
|
||||
|
||||
public:
|
||||
HTTPDirectory(HTTPRepoPrivate* repo, const std::string& path) :
|
||||
_repository(repo),
|
||||
@@ -124,6 +166,8 @@ public:
|
||||
// already exists on disk
|
||||
parseDirIndex(children);
|
||||
std::sort(children.begin(), children.end());
|
||||
|
||||
parseHashCache();
|
||||
} catch (sg_exception& ) {
|
||||
// parsing cache failed
|
||||
children.clear();
|
||||
@@ -149,7 +193,7 @@ public:
|
||||
{
|
||||
SGPath fpath(absolutePath());
|
||||
fpath.append(".dirindex");
|
||||
_repository->updatedFileContents(fpath, hash);
|
||||
updatedFileContents(fpath, hash);
|
||||
|
||||
children.clear();
|
||||
parseDirIndex(children);
|
||||
@@ -174,45 +218,64 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
char* buf = nullptr;
|
||||
size_t bufSize = 0;
|
||||
char* buf = nullptr;
|
||||
size_t bufSize = 0;
|
||||
|
||||
for (const auto& child : children) {
|
||||
if (child.type != HTTPRepository::FileType)
|
||||
for (auto& child : children) {
|
||||
if (child.type != HTTPRepository::FileType)
|
||||
continue;
|
||||
|
||||
if (child.path.exists())
|
||||
continue;
|
||||
|
||||
SGPath cp = _repository->installedCopyPath;
|
||||
cp.append(relativePath());
|
||||
cp.append(child.name);
|
||||
if (!cp.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SGBinaryFile src(cp);
|
||||
SGBinaryFile dst(child.path);
|
||||
src.open(SG_IO_IN);
|
||||
dst.open(SG_IO_OUT);
|
||||
|
||||
if (bufSize < cp.sizeInBytes()) {
|
||||
bufSize = cp.sizeInBytes();
|
||||
free(buf);
|
||||
buf = (char*)malloc(bufSize);
|
||||
if (!buf) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (child.path.exists())
|
||||
continue;
|
||||
src.read(buf, cp.sizeInBytes());
|
||||
dst.write(buf, cp.sizeInBytes());
|
||||
src.close();
|
||||
dst.close();
|
||||
|
||||
SGPath cp = _repository->installedCopyPath;
|
||||
cp.append(relativePath());
|
||||
cp.append(child.name);
|
||||
if (!cp.exists()) {
|
||||
continue;
|
||||
}
|
||||
// reset caching
|
||||
child.path.set_cached(false);
|
||||
child.path.set_cached(true);
|
||||
|
||||
SGBinaryFile src(cp);
|
||||
SGBinaryFile dst(child.path);
|
||||
src.open(SG_IO_IN);
|
||||
dst.open(SG_IO_OUT);
|
||||
std::string hash = computeHashForPath(child.path);
|
||||
updatedFileContents(child.path, hash);
|
||||
}
|
||||
|
||||
if (bufSize < cp.sizeInBytes()) {
|
||||
bufSize = cp.sizeInBytes();
|
||||
free(buf);
|
||||
buf = (char*) malloc(bufSize);
|
||||
if (!buf) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
src.read(buf, cp.sizeInBytes());
|
||||
dst.write(buf, cp.sizeInBytes());
|
||||
src.close();
|
||||
dst.close();
|
||||
/// helper to check and erase 'fooBar' from paths, if passed fooBar.zip, fooBar.tgz, etc.
|
||||
void removeExtractedDirectoryFromList(PathList& paths, const std::string& tarballName)
|
||||
{
|
||||
const auto directoryName = SGPath::fromUtf8(tarballName).file_base();
|
||||
auto it = std::find_if(paths.begin(), paths.end(), [directoryName](const SGPath& p) {
|
||||
return p.isDir() && (p.file() == directoryName);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
free(buf);
|
||||
if (it != paths.end()) {
|
||||
paths.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void updateChildrenBasedOnHash()
|
||||
@@ -248,6 +311,11 @@ public:
|
||||
orphans.end());
|
||||
}
|
||||
|
||||
// ensure the extracted directory corresponding to a tarball, is *not* considered an orphan
|
||||
if (c.type == HTTPRepository::TarballType) {
|
||||
removeExtractedDirectoryFromList(orphans, c.name);
|
||||
}
|
||||
|
||||
if (_repository->syncPredicate) {
|
||||
const auto pathOnDisk = isNew ? absolutePath() / c.name : *p;
|
||||
// never handle deletes here, do them at the end
|
||||
@@ -270,11 +338,10 @@ public:
|
||||
toBeUpdated.push_back(c);
|
||||
} else {
|
||||
// File/Directory exists and hash is valid.
|
||||
|
||||
if (c.type == HTTPRepository::DirectoryType) {
|
||||
// If it's a directory,perform a recursive check.
|
||||
HTTPDirectory *childDir = childDirectory(c.name);
|
||||
childDir->updateChildrenBasedOnHash();
|
||||
_repository->scheduleUpdateOfChildren(childDir);
|
||||
}
|
||||
}
|
||||
} // of repository-defined (well, .dirIndex) children iteration
|
||||
@@ -310,7 +377,12 @@ public:
|
||||
|
||||
// We now have a list of entries that need to be updated, and a list
|
||||
// of orphan files that should be removed.
|
||||
removeOrphans(orphans);
|
||||
try {
|
||||
removeOrphans(orphans);
|
||||
} catch (sg_exception& e) {
|
||||
_repository->failedToUpdateChild(_relativePath, HTTPRepository::ResultCode::REPO_ERROR_IO);
|
||||
}
|
||||
|
||||
scheduleUpdates(toBeUpdated);
|
||||
}
|
||||
|
||||
@@ -322,11 +394,10 @@ public:
|
||||
|
||||
void removeOrphans(const PathList orphans)
|
||||
{
|
||||
PathList::const_iterator it;
|
||||
for (it = orphans.begin(); it != orphans.end(); ++it) {
|
||||
if (it->file() == ".dirindex") continue;
|
||||
if (it->file() == ".hash") continue;
|
||||
removeChild(*it);
|
||||
for (const auto& o : orphans) {
|
||||
if (o.file() == ".dirindex") continue;
|
||||
if (o.file() == ".hash") continue;
|
||||
removeChild(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +443,80 @@ public:
|
||||
return _relativePath;
|
||||
}
|
||||
|
||||
class ArchiveExtractTask {
|
||||
public:
|
||||
ArchiveExtractTask(SGPath p, const std::string &relPath)
|
||||
: relativePath(relPath), file(p), extractor(p.dir()) {
|
||||
if (!file.open(SG_IO_IN)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT,
|
||||
"Unable to open " << p << " to extract");
|
||||
return;
|
||||
}
|
||||
|
||||
compressedBytes = p.sizeInBytes();
|
||||
buffer = (uint8_t *)malloc(bufferSize);
|
||||
}
|
||||
|
||||
ArchiveExtractTask(const ArchiveExtractTask &) = delete;
|
||||
|
||||
HTTPRepoPrivate::ProcessResult run(HTTPRepoPrivate* repo)
|
||||
{
|
||||
if (!buffer) {
|
||||
return HTTPRepoPrivate::ProcessFailed;
|
||||
}
|
||||
|
||||
size_t rd = file.read((char*)buffer, bufferSize);
|
||||
repo->bytesExtracted += rd;
|
||||
extractor.extractBytes(buffer, rd);
|
||||
|
||||
if (file.eof()) {
|
||||
extractor.flush();
|
||||
file.close();
|
||||
|
||||
if (!extractor.isAtEndOfArchive()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Corrupt tarball " << relativePath);
|
||||
repo->failedToUpdateChild(relativePath,
|
||||
HTTPRepository::REPO_ERROR_IO);
|
||||
return HTTPRepoPrivate::ProcessFailed;
|
||||
}
|
||||
|
||||
if (extractor.hasError()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Error extracting " << relativePath);
|
||||
repo->failedToUpdateChild(relativePath,
|
||||
HTTPRepository::REPO_ERROR_IO);
|
||||
return HTTPRepoPrivate::ProcessFailed;
|
||||
}
|
||||
|
||||
return HTTPRepoPrivate::ProcessDone;
|
||||
}
|
||||
|
||||
return HTTPRepoPrivate::ProcessContinue;
|
||||
}
|
||||
|
||||
size_t archiveSizeBytes() const
|
||||
{
|
||||
return compressedBytes;
|
||||
}
|
||||
|
||||
~ArchiveExtractTask() { free(buffer); }
|
||||
|
||||
|
||||
private:
|
||||
// intentionally small so we extract incrementally on Windows
|
||||
// where Defender throttles many small files, sorry
|
||||
// if you make this bigger we will be more efficient but stall for
|
||||
// longer when extracting the Airports_archive
|
||||
const int bufferSize = 1024 * 64;
|
||||
|
||||
std::string relativePath;
|
||||
uint8_t *buffer = nullptr;
|
||||
SGBinaryFile file;
|
||||
ArchiveExtractor extractor;
|
||||
std::size_t compressedBytes;
|
||||
};
|
||||
|
||||
using ArchiveExtractTaskPtr = std::shared_ptr<ArchiveExtractTask>;
|
||||
|
||||
void didUpdateFile(const std::string& file, const std::string& hash, size_t sz)
|
||||
{
|
||||
// check hash matches what we expected
|
||||
@@ -387,60 +532,49 @@ public:
|
||||
_relativePath + "/" + file,
|
||||
HTTPRepository::REPO_ERROR_CHECKSUM);
|
||||
} else {
|
||||
_repository->updatedFileContents(it->path, hash);
|
||||
updatedFileContents(it->path, hash);
|
||||
_repository->updatedChildSuccessfully(_relativePath + "/" +
|
||||
file);
|
||||
|
||||
_repository->totalDownloaded += sz;
|
||||
SGPath p = SGPath(absolutePath(), file);
|
||||
|
||||
if ((p.extension() == "tgz") || (p.extension() == "zip")) {
|
||||
// We require that any compressed files have the same filename as the file or directory
|
||||
// they expand to, so we can remove the old file/directory before extracting the new
|
||||
// data.
|
||||
SGPath removePath = SGPath(p.base());
|
||||
bool pathAvailable = true;
|
||||
if (removePath.exists()) {
|
||||
if (removePath.isDir()) {
|
||||
simgear::Dir pd(removePath);
|
||||
pathAvailable = pd.removeChildren();
|
||||
if (it->type == HTTPRepository::TarballType) {
|
||||
// We require that any compressed files have the same filename as the file or directory
|
||||
// they expand to, so we can remove the old file/directory before extracting the new
|
||||
// data.
|
||||
SGPath removePath = SGPath(p.base());
|
||||
bool pathAvailable = true;
|
||||
if (removePath.exists()) {
|
||||
if (removePath.isDir()) {
|
||||
simgear::Dir pd(removePath);
|
||||
pathAvailable = pd.removeChildren();
|
||||
} else {
|
||||
pathAvailable = removePath.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (pathAvailable) {
|
||||
// we use a Task helper to extract tarballs incrementally.
|
||||
// without this, archive extraction blocks here, which
|
||||
// prevents other repositories downloading / updating.
|
||||
// Unfortunately due Windows AV (Defender, etc) we cna block
|
||||
// here for many minutes.
|
||||
|
||||
// use a lambda to own this shared_ptr; this means when the
|
||||
// lambda is destroyed, the ArchiveExtraTask will get
|
||||
// cleaned up.
|
||||
ArchiveExtractTaskPtr t =
|
||||
std::make_shared<ArchiveExtractTask>(p, _relativePath);
|
||||
auto cb = [t](HTTPRepoPrivate* repo) {
|
||||
return t->run(repo);
|
||||
};
|
||||
_repository->bytesToExtract += t->archiveSizeBytes();
|
||||
_repository->addTask(cb);
|
||||
} else {
|
||||
pathAvailable = removePath.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (pathAvailable) {
|
||||
// If this is a tarball, then extract it.
|
||||
SGBinaryFile f(p);
|
||||
if (! f.open(SG_IO_IN)) SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to open " << p << " to extract");
|
||||
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "Extracting " << absolutePath() << "/" << file << " to " << p.dir());
|
||||
SGPath extractDir = p.dir();
|
||||
ArchiveExtractor ex(extractDir);
|
||||
|
||||
uint8_t *buf = (uint8_t *)alloca(2048);
|
||||
while (!f.eof()) {
|
||||
size_t bufSize = f.read((char *)buf, 2048);
|
||||
ex.extractBytes(buf, bufSize);
|
||||
}
|
||||
|
||||
ex.flush();
|
||||
if (! ex.isAtEndOfArchive()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Corrupt tarball " << p);
|
||||
_repository->failedToUpdateChild(
|
||||
_relativePath, HTTPRepository::REPO_ERROR_IO);
|
||||
}
|
||||
|
||||
if (ex.hasError()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Error extracting " << p);
|
||||
_repository->failedToUpdateChild(
|
||||
_relativePath, HTTPRepository::REPO_ERROR_IO);
|
||||
}
|
||||
|
||||
} else {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath);
|
||||
} // of pathAvailable
|
||||
} // of handling tgz files
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath);
|
||||
} // of pathAvailable
|
||||
} // of handling archive files
|
||||
} // of hash matches
|
||||
} // of found in child list
|
||||
}
|
||||
@@ -452,6 +586,50 @@ public:
|
||||
fpath.append(file);
|
||||
_repository->failedToUpdateChild(fpath, status);
|
||||
}
|
||||
|
||||
std::string hashForPath(const SGPath& p) const
|
||||
{
|
||||
const auto ps = p.utf8Str();
|
||||
auto it = hashes.find(ps);
|
||||
if (it != hashes.end()) {
|
||||
const auto& entry = it->second;
|
||||
// ensure data on disk hasn't changed.
|
||||
// we could also use the file type here if we were paranoid
|
||||
if ((p.sizeInBytes() == entry.lengthBytes) && (p.modTime() == entry.modTime)) {
|
||||
return entry.hashHex;
|
||||
}
|
||||
|
||||
// entry in the cache, but it's stale so remove and fall through
|
||||
hashes.erase(it);
|
||||
}
|
||||
|
||||
std::string hash = computeHashForPath(p);
|
||||
updatedFileContents(p, hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool isHashCacheDirty() const
|
||||
{
|
||||
return hashCacheDirty;
|
||||
}
|
||||
|
||||
void writeHashCache() const
|
||||
{
|
||||
if (!hashCacheDirty)
|
||||
return;
|
||||
|
||||
hashCacheDirty = false;
|
||||
|
||||
SGPath cachePath = absolutePath() / ".dirhash";
|
||||
sg_ofstream stream(cachePath, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
for (const auto& e : hashes) {
|
||||
const auto& entry = e.second;
|
||||
stream << entry.filePath << "*" << entry.modTime << "*"
|
||||
<< entry.lengthBytes << "*" << entry.hashHex << "\n";
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
struct ChildWithName
|
||||
@@ -575,7 +753,7 @@ private:
|
||||
ok = _repository->deleteDirectory(fpath, path);
|
||||
} else {
|
||||
// remove the hash cache entry
|
||||
_repository->updatedFileContents(path, std::string());
|
||||
updatedFileContents(path, std::string());
|
||||
ok = path.remove();
|
||||
}
|
||||
|
||||
@@ -588,14 +766,83 @@ private:
|
||||
std::string hashForChild(const ChildInfo& child) const
|
||||
{
|
||||
SGPath p(child.path);
|
||||
if (child.type == HTTPRepository::DirectoryType)
|
||||
p.append(".dirindex");
|
||||
if (child.type == HTTPRepository::TarballType)
|
||||
p.concat(
|
||||
".tgz"); // For tarballs the hash is against the tarball file itself
|
||||
return _repository->hashForPath(p);
|
||||
if (child.type == HTTPRepository::DirectoryType) {
|
||||
p.append(".dirindex");
|
||||
}
|
||||
return hashForPath(p);
|
||||
}
|
||||
|
||||
void parseHashCache()
|
||||
{
|
||||
hashes.clear();
|
||||
SGPath cachePath = absolutePath() / ".dirhash";
|
||||
if (!cachePath.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sg_ifstream stream(cachePath, std::ios::in);
|
||||
|
||||
while (!stream.eof()) {
|
||||
std::string line;
|
||||
std::getline(stream, line);
|
||||
line = simgear::strutils::strip(line);
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
string_list tokens = simgear::strutils::split(line, "*");
|
||||
if (tokens.size() < 4) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "invalid entry in '" << cachePath << "': '" << line << "' (ignoring line)");
|
||||
continue;
|
||||
}
|
||||
const std::string nameData = simgear::strutils::strip(tokens[0]);
|
||||
const std::string timeData = simgear::strutils::strip(tokens[1]);
|
||||
const std::string sizeData = simgear::strutils::strip(tokens[2]);
|
||||
const std::string hashData = simgear::strutils::strip(tokens[3]);
|
||||
|
||||
if (nameData.empty() || timeData.empty() || sizeData.empty() || hashData.empty()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "invalid entry in '" << cachePath << "': '" << line << "' (ignoring line)");
|
||||
continue;
|
||||
}
|
||||
|
||||
HashCacheEntry entry;
|
||||
entry.filePath = nameData;
|
||||
entry.hashHex = hashData;
|
||||
entry.modTime = strtol(timeData.c_str(), NULL, 10);
|
||||
entry.lengthBytes = strtol(sizeData.c_str(), NULL, 10);
|
||||
hashes.insert(std::make_pair(entry.filePath, entry));
|
||||
}
|
||||
}
|
||||
|
||||
void updatedFileContents(const SGPath& p, const std::string& newHash) const
|
||||
{
|
||||
// remove the existing entry
|
||||
const auto ps = p.utf8Str();
|
||||
auto it = hashes.find(ps);
|
||||
if (it != hashes.end()) {
|
||||
hashes.erase(it);
|
||||
hashCacheDirty = true;
|
||||
}
|
||||
|
||||
if (newHash.empty()) {
|
||||
return; // we're done
|
||||
}
|
||||
|
||||
// use a cloned SGPath and reset its caching to force one stat() call
|
||||
SGPath p2(p);
|
||||
p2.set_cached(false);
|
||||
p2.set_cached(true);
|
||||
|
||||
HashCacheEntry entry;
|
||||
entry.filePath = ps;
|
||||
entry.hashHex = newHash;
|
||||
entry.modTime = p2.modTime();
|
||||
entry.lengthBytes = p2.sizeInBytes();
|
||||
hashes.insert(std::make_pair(ps, entry));
|
||||
|
||||
hashCacheDirty = true;
|
||||
}
|
||||
|
||||
|
||||
HTTPRepoPrivate* _repository;
|
||||
std::string _relativePath; // in URL and file-system space
|
||||
};
|
||||
@@ -606,7 +853,6 @@ HTTPRepository::HTTPRepository(const SGPath& base, HTTP::Client *cl) :
|
||||
_d->http = cl;
|
||||
_d->basePath = base;
|
||||
_d->rootDir.reset(new HTTPDirectory(_d.get(), ""));
|
||||
_d->parseHashCache();
|
||||
}
|
||||
|
||||
HTTPRepository::~HTTPRepository()
|
||||
@@ -654,6 +900,38 @@ bool HTTPRepository::isDoingSync() const
|
||||
return _d->isUpdating;
|
||||
}
|
||||
|
||||
void HTTPRepository::process()
|
||||
{
|
||||
int processedCount = 0;
|
||||
const int maxToProcess = 16;
|
||||
|
||||
while (processedCount < maxToProcess) {
|
||||
if (_d->pendingTasks.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto task = _d->pendingTasks.front();
|
||||
auto result = task(_d.get());
|
||||
if (result == HTTPRepoPrivate::ProcessContinue) {
|
||||
// assume we're not complete
|
||||
return;
|
||||
}
|
||||
|
||||
_d->pendingTasks.pop_front();
|
||||
++processedCount;
|
||||
}
|
||||
|
||||
_d->checkForComplete();
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::checkForComplete()
|
||||
{
|
||||
if (pendingTasks.empty() && activeRequests.empty() &&
|
||||
queuedRequests.empty()) {
|
||||
isUpdating = false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t HTTPRepository::bytesToDownload() const
|
||||
{
|
||||
size_t result = 0;
|
||||
@@ -687,6 +965,11 @@ size_t HTTPRepository::bytesDownloaded() const
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t HTTPRepository::bytesToExtract() const
|
||||
{
|
||||
return _d->bytesToExtract - _d->bytesExtracted;
|
||||
}
|
||||
|
||||
void HTTPRepository::setInstalledCopyPath(const SGPath& copyPath)
|
||||
{
|
||||
_d->installedCopyPath = copyPath;
|
||||
@@ -731,49 +1014,71 @@ HTTPRepository::failure() const
|
||||
}
|
||||
|
||||
protected:
|
||||
void gotBodyData(const char *s, int n) override {
|
||||
if (!file.get()) {
|
||||
file.reset(new SGBinaryFile(pathInRepo));
|
||||
if (!file->open(SG_IO_OUT)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"unable to create file " << pathInRepo);
|
||||
_directory->repository()->http->cancelRequest(
|
||||
this, "Unable to create output file:" + pathInRepo.utf8Str());
|
||||
}
|
||||
void gotBodyData(const char* s, int n) override
|
||||
{
|
||||
if (!file.get()) {
|
||||
const bool ok = createOutputFile();
|
||||
if (!ok) {
|
||||
_directory->repository()->http->cancelRequest(
|
||||
this, "Unable to create output file:" + pathInRepo.utf8Str());
|
||||
}
|
||||
}
|
||||
|
||||
sha1_init(&hashContext);
|
||||
sha1_write(&hashContext, s, n);
|
||||
file->write(s, n);
|
||||
}
|
||||
|
||||
sha1_write(&hashContext, s, n);
|
||||
file->write(s, n);
|
||||
}
|
||||
bool createOutputFile()
|
||||
{
|
||||
file.reset(new SGBinaryFile(pathInRepo));
|
||||
if (!file->open(SG_IO_OUT)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"unable to create file " << pathInRepo);
|
||||
return false;
|
||||
}
|
||||
|
||||
void onDone() override {
|
||||
file->close();
|
||||
if (responseCode() == 200) {
|
||||
std::string hash =
|
||||
strutils::encodeHex(sha1_result(&hashContext), HASH_LENGTH);
|
||||
_directory->didUpdateFile(fileName, hash, contentSize());
|
||||
} else if (responseCode() == 404) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"terrasync file not found on server: "
|
||||
<< fileName << " for " << _directory->absolutePath());
|
||||
_directory->didFailToUpdateFile(
|
||||
fileName, HTTPRepository::REPO_ERROR_FILE_NOT_FOUND);
|
||||
} else {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"terrasync file download error on server: "
|
||||
<< fileName << " for " << _directory->absolutePath()
|
||||
<< "\n\tserver responded: " << responseCode() << "/"
|
||||
<< responseReason());
|
||||
_directory->didFailToUpdateFile(fileName,
|
||||
HTTPRepository::REPO_ERROR_HTTP);
|
||||
// should we every retry here?
|
||||
sha1_init(&hashContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
_directory->repository()->finishedRequest(
|
||||
this, HTTPRepoPrivate::RequestFinish::Done);
|
||||
}
|
||||
void onDone() override
|
||||
{
|
||||
const bool is200Response = (responseCode() == 200);
|
||||
if (!file && is200Response) {
|
||||
// if the server defines a zero-byte file, we will never call
|
||||
// gotBodyData, so create the file here
|
||||
// this ensures all the logic below works as expected
|
||||
createOutputFile();
|
||||
}
|
||||
|
||||
if (file) {
|
||||
file->close();
|
||||
}
|
||||
|
||||
if (is200Response) {
|
||||
std::string hash =
|
||||
strutils::encodeHex(sha1_result(&hashContext), HASH_LENGTH);
|
||||
_directory->didUpdateFile(fileName, hash, contentSize());
|
||||
} else if (responseCode() == 404) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"terrasync file not found on server: "
|
||||
<< fileName << " for " << _directory->absolutePath());
|
||||
_directory->didFailToUpdateFile(
|
||||
fileName, HTTPRepository::REPO_ERROR_FILE_NOT_FOUND);
|
||||
} else {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"terrasync file download error on server: "
|
||||
<< fileName << " for " << _directory->absolutePath()
|
||||
<< "\n\tserver responded: " << responseCode() << "/"
|
||||
<< responseReason());
|
||||
_directory->didFailToUpdateFile(fileName,
|
||||
HTTPRepository::REPO_ERROR_HTTP);
|
||||
// should we every retry here?
|
||||
}
|
||||
|
||||
_directory->repository()->finishedRequest(
|
||||
this, HTTPRepoPrivate::RequestFinish::Done);
|
||||
}
|
||||
|
||||
void onFail() override {
|
||||
HTTPRepository::ResultCode code = HTTPRepository::REPO_ERROR_SOCKET;
|
||||
@@ -868,7 +1173,7 @@ HTTPRepository::failure() const
|
||||
return;
|
||||
}
|
||||
|
||||
std::string curHash = _directory->repository()->hashForPath(path());
|
||||
std::string curHash = _directory->hashForPath(path());
|
||||
if (hash != curHash) {
|
||||
simgear::Dir d(_directory->absolutePath());
|
||||
if (!d.exists()) {
|
||||
@@ -967,6 +1272,7 @@ HTTPRepository::failure() const
|
||||
http->cancelRequest(*rq, "Repository object deleted");
|
||||
}
|
||||
|
||||
flushHashCaches();
|
||||
directories.clear(); // wil delete them all
|
||||
}
|
||||
|
||||
@@ -986,154 +1292,6 @@ HTTPRepository::failure() const
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
class HashEntryWithPath
|
||||
{
|
||||
public:
|
||||
HashEntryWithPath(const SGPath& p) : path(p.utf8Str()) {}
|
||||
bool operator()(const HTTPRepoPrivate::HashCacheEntry& entry) const
|
||||
{ return entry.filePath == path; }
|
||||
private:
|
||||
std::string path;
|
||||
};
|
||||
|
||||
std::string HTTPRepoPrivate::hashForPath(const SGPath& p)
|
||||
{
|
||||
HashCache::iterator it = std::find_if(hashes.begin(), hashes.end(), HashEntryWithPath(p));
|
||||
if (it != hashes.end()) {
|
||||
// ensure data on disk hasn't changed.
|
||||
// we could also use the file type here if we were paranoid
|
||||
if ((p.sizeInBytes() == it->lengthBytes) && (p.modTime() == it->modTime)) {
|
||||
return it->hashHex;
|
||||
}
|
||||
|
||||
// entry in the cache, but it's stale so remove and fall through
|
||||
hashes.erase(it);
|
||||
}
|
||||
|
||||
std::string hash = computeHashForPath(p);
|
||||
updatedFileContents(p, hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string HTTPRepoPrivate::computeHashForPath(const SGPath& p)
|
||||
{
|
||||
if (!p.exists())
|
||||
return {};
|
||||
|
||||
sha1nfo info;
|
||||
sha1_init(&info);
|
||||
|
||||
const int bufSize = 1024 * 1024;
|
||||
char* buf = static_cast<char*>(malloc(bufSize));
|
||||
if (!buf) {
|
||||
sg_io_exception("Couldn't allocate SHA1 computation buffer");
|
||||
}
|
||||
|
||||
size_t readLen;
|
||||
SGBinaryFile f(p);
|
||||
if (!f.open(SG_IO_IN)) {
|
||||
free(buf);
|
||||
throw sg_io_exception("Couldn't open file for compute hash", p);
|
||||
}
|
||||
while ((readLen = f.read(buf, bufSize)) > 0) {
|
||||
sha1_write(&info, buf, readLen);
|
||||
}
|
||||
|
||||
f.close();
|
||||
free(buf);
|
||||
std::string hashBytes((char*) sha1_result(&info), HASH_LENGTH);
|
||||
return strutils::encodeHex(hashBytes);
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::updatedFileContents(const SGPath& p, const std::string& newHash)
|
||||
{
|
||||
// remove the existing entry
|
||||
auto it = std::find_if(hashes.begin(), hashes.end(), HashEntryWithPath(p));
|
||||
if (it != hashes.end()) {
|
||||
hashes.erase(it);
|
||||
++hashCacheDirty;
|
||||
}
|
||||
|
||||
if (newHash.empty()) {
|
||||
return; // we're done
|
||||
}
|
||||
|
||||
// use a cloned SGPath and reset its caching to force one stat() call
|
||||
SGPath p2(p);
|
||||
p2.set_cached(false);
|
||||
p2.set_cached(true);
|
||||
|
||||
HashCacheEntry entry;
|
||||
entry.filePath = p.utf8Str();
|
||||
entry.hashHex = newHash;
|
||||
entry.modTime = p2.modTime();
|
||||
entry.lengthBytes = p2.sizeInBytes();
|
||||
hashes.push_back(entry);
|
||||
|
||||
++hashCacheDirty ;
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::writeHashCache()
|
||||
{
|
||||
if (hashCacheDirty == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SGPath cachePath = basePath;
|
||||
cachePath.append(".hashes");
|
||||
sg_ofstream stream(cachePath, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
HashCache::const_iterator it;
|
||||
for (it = hashes.begin(); it != hashes.end(); ++it) {
|
||||
stream << it->filePath << "*" << it->modTime << "*"
|
||||
<< it->lengthBytes << "*" << it->hashHex << "\n";
|
||||
}
|
||||
stream.close();
|
||||
hashCacheDirty = 0;
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::parseHashCache()
|
||||
{
|
||||
hashes.clear();
|
||||
SGPath cachePath = basePath;
|
||||
cachePath.append(".hashes");
|
||||
if (!cachePath.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sg_ifstream stream(cachePath, std::ios::in);
|
||||
|
||||
while (!stream.eof()) {
|
||||
std::string line;
|
||||
std::getline(stream,line);
|
||||
line = simgear::strutils::strip(line);
|
||||
if( line.empty() || line[0] == '#' )
|
||||
continue;
|
||||
|
||||
string_list tokens = simgear::strutils::split(line, "*");
|
||||
if( tokens.size() < 4 ) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "invalid entry in '" << cachePath << "': '" << line << "' (ignoring line)");
|
||||
continue;
|
||||
}
|
||||
const std::string nameData = simgear::strutils::strip(tokens[0]);
|
||||
const std::string timeData = simgear::strutils::strip(tokens[1]);
|
||||
const std::string sizeData = simgear::strutils::strip(tokens[2]);
|
||||
const std::string hashData = simgear::strutils::strip(tokens[3]);
|
||||
|
||||
if (nameData.empty() || timeData.empty() || sizeData.empty() || hashData.empty() ) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "invalid entry in '" << cachePath << "': '" << line << "' (ignoring line)");
|
||||
continue;
|
||||
}
|
||||
|
||||
HashCacheEntry entry;
|
||||
entry.filePath = nameData;
|
||||
entry.hashHex = hashData;
|
||||
entry.modTime = strtol(timeData.c_str(), NULL, 10);
|
||||
entry.lengthBytes = strtol(sizeData.c_str(), NULL, 10);
|
||||
hashes.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
class DirectoryWithPath
|
||||
{
|
||||
public:
|
||||
@@ -1163,16 +1321,12 @@ HTTPRepository::failure() const
|
||||
if (it != directories.end()) {
|
||||
assert((*it)->absolutePath() == absPath);
|
||||
directories.erase(it);
|
||||
} else {
|
||||
// we encounter this code path when deleting an orphaned directory
|
||||
}
|
||||
|
||||
Dir dir(absPath);
|
||||
bool result = dir.remove(true);
|
||||
|
||||
// update the hash cache too
|
||||
updatedFileContents(absPath, std::string());
|
||||
} else {
|
||||
// we encounter this code path when deleting an orphaned directory
|
||||
}
|
||||
|
||||
Dir dir(absPath);
|
||||
bool result = dir.remove(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1208,17 +1362,10 @@ HTTPRepository::failure() const
|
||||
http->makeRequest(rr);
|
||||
}
|
||||
|
||||
// rate limit how often we write this, since otherwise
|
||||
// it dominates the time on Windows. 256 seems about right,
|
||||
// causes a write a few times a minute.
|
||||
if (hashCacheDirty > 256) {
|
||||
writeHashCache();
|
||||
}
|
||||
|
||||
if (activeRequests.empty() && queuedRequests.empty()) {
|
||||
isUpdating = false;
|
||||
writeHashCache();
|
||||
if (countDirtyHashCaches() > 32) {
|
||||
flushHashCaches();
|
||||
}
|
||||
checkForComplete();
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::failedToGetRootIndex(HTTPRepository::ResultCode st)
|
||||
@@ -1279,4 +1426,38 @@ HTTPRepository::failure() const
|
||||
failures.end());
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::scheduleUpdateOfChildren(HTTPDirectory* dir)
|
||||
{
|
||||
auto updateChildTask = [dir](const HTTPRepoPrivate *) {
|
||||
dir->updateChildrenBasedOnHash();
|
||||
return ProcessDone;
|
||||
};
|
||||
|
||||
addTask(updateChildTask);
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::addTask(RepoProcessTask task) {
|
||||
pendingTasks.push_back(task);
|
||||
}
|
||||
|
||||
int HTTPRepoPrivate::countDirtyHashCaches() const
|
||||
{
|
||||
int result = rootDir->isHashCacheDirty() ? 1 : 0;
|
||||
for (const auto& dir : directories) {
|
||||
if (dir->isHashCacheDirty()) {
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void HTTPRepoPrivate::flushHashCaches()
|
||||
{
|
||||
rootDir->writeHashCache();
|
||||
for (const auto& dir : directories) {
|
||||
dir->writeHashCache();
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -62,12 +62,19 @@ public:
|
||||
|
||||
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.
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
@@ -48,40 +51,32 @@ protected:
|
||||
size_t _contentSize = 0;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<HTTPRepoGetRequest> RepoRequestPtr;
|
||||
using RepoRequestPtr = SGSharedPtr<HTTPRepoGetRequest>;
|
||||
|
||||
class HTTPRepoPrivate {
|
||||
public:
|
||||
struct HashCacheEntry {
|
||||
std::string filePath;
|
||||
time_t modTime;
|
||||
size_t lengthBytes;
|
||||
std::string hashHex;
|
||||
};
|
||||
|
||||
typedef std::vector<HashCacheEntry> HashCache;
|
||||
HashCache hashes;
|
||||
int hashCacheDirty = 0;
|
||||
|
||||
HTTPRepository::FailureVec failures;
|
||||
int maxPermittedFailures = 16;
|
||||
|
||||
HTTPRepoPrivate(HTTPRepository *parent)
|
||||
: p(parent), isUpdating(false), status(HTTPRepository::REPO_NO_ERROR),
|
||||
totalDownloaded(0) {
|
||||
;
|
||||
HTTPRepoPrivate(HTTPRepository* parent)
|
||||
: p(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~HTTPRepoPrivate();
|
||||
|
||||
HTTPRepository *p; // link back to outer
|
||||
HTTP::Client *http;
|
||||
HTTP::Client* http = nullptr;
|
||||
std::string baseUrl;
|
||||
SGPath basePath;
|
||||
bool isUpdating;
|
||||
HTTPRepository::ResultCode status;
|
||||
bool isUpdating = false;
|
||||
HTTPRepository::ResultCode status = HTTPRepository::REPO_NO_ERROR;
|
||||
HTTPDirectory_ptr rootDir;
|
||||
size_t totalDownloaded;
|
||||
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,
|
||||
@@ -89,18 +84,14 @@ public:
|
||||
HTTP::Request_ptr updateDir(HTTPDirectory *dir, const std::string &hash,
|
||||
size_t sz);
|
||||
|
||||
std::string hashForPath(const SGPath &p);
|
||||
void updatedFileContents(const SGPath &p, const std::string &newHash);
|
||||
void parseHashCache();
|
||||
std::string computeHashForPath(const SGPath &p);
|
||||
void writeHashCache();
|
||||
|
||||
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;
|
||||
|
||||
@@ -113,10 +104,24 @@ public:
|
||||
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
|
||||
|
||||
@@ -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,41 @@
|
||||
// $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 {
|
||||
return {gzMsg};
|
||||
}
|
||||
}
|
||||
|
||||
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 +65,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 +74,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 +92,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 +101,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 +119,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 +127,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 +144,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 +152,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 +167,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 +175,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 +192,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 +200,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 +217,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 +225,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 +242,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 +250,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 +265,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 +273,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 +298,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 +330,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 +338,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 +347,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 +357,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 +382,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 +393,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 +418,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 +428,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 +453,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 +464,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 +489,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
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/math/SGGeometry.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
@@ -453,10 +454,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 +461,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 +491,309 @@ 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;
|
||||
// zero out structures
|
||||
gbs_center = SGVec3d(0, 0, 0);
|
||||
gbs_radius = 0.0;
|
||||
|
||||
wgs84_nodes.clear();
|
||||
normals.clear();
|
||||
texcoords.clear();
|
||||
wgs84_nodes.clear();
|
||||
normals.clear();
|
||||
texcoords.clear();
|
||||
|
||||
pts_v.clear();
|
||||
pts_n.clear();
|
||||
pts_c.clear();
|
||||
pts_tcs.clear();
|
||||
pts_vas.clear();
|
||||
pt_materials.clear();
|
||||
pts_v.clear();
|
||||
pts_n.clear();
|
||||
pts_c.clear();
|
||||
pts_tcs.clear();
|
||||
pts_vas.clear();
|
||||
pt_materials.clear();
|
||||
|
||||
tris_v.clear();
|
||||
tris_n.clear();
|
||||
tris_c.clear();
|
||||
tris_tcs.clear();
|
||||
tris_vas.clear();
|
||||
tri_materials.clear();
|
||||
tris_v.clear();
|
||||
tris_n.clear();
|
||||
tris_c.clear();
|
||||
tris_tcs.clear();
|
||||
tris_vas.clear();
|
||||
tri_materials.clear();
|
||||
|
||||
strips_v.clear();
|
||||
strips_n.clear();
|
||||
strips_c.clear();
|
||||
strips_tcs.clear();
|
||||
strips_vas.clear();
|
||||
strip_materials.clear();
|
||||
strips_v.clear();
|
||||
strips_n.clear();
|
||||
strips_c.clear();
|
||||
strips_tcs.clear();
|
||||
strips_vas.clear();
|
||||
strip_materials.clear();
|
||||
|
||||
fans_v.clear();
|
||||
fans_n.clear();
|
||||
fans_c.clear();
|
||||
fans_tcs.clear();
|
||||
fans_vas.clear();
|
||||
fan_materials.clear();
|
||||
fans_v.clear();
|
||||
fans_n.clear();
|
||||
fans_c.clear();
|
||||
fans_tcs.clear();
|
||||
fans_vas.clear();
|
||||
fan_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!");
|
||||
|
||||
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 +967,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,9 @@ std::string SGFile::computeHash()
|
||||
[](char* p) { free(p); }};
|
||||
|
||||
if (!buf) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check");
|
||||
// @TODO report out of memory error
|
||||
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check:" << file_name);
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t readLen;
|
||||
|
||||
BIN
simgear/io/test.tar.xz
Normal file
BIN
simgear/io/test.tar.xz
Normal file
Binary file not shown.
@@ -31,6 +31,10 @@ 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.
|
||||
@@ -409,6 +413,7 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
|
||||
cl->update();
|
||||
testServer.poll();
|
||||
|
||||
repo->process();
|
||||
if (!repo->isDoingSync()) {
|
||||
return;
|
||||
}
|
||||
@@ -423,6 +428,7 @@ void runForTime(HTTP::Client *cl, HTTPRepository *repo, int msec = 15) {
|
||||
while (start.elapsedMSec() < msec) {
|
||||
cl->update();
|
||||
testServer.poll();
|
||||
repo->process();
|
||||
SGTimeStamp::sleepForMSec(1);
|
||||
}
|
||||
}
|
||||
@@ -444,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++;
|
||||
@@ -894,6 +901,27 @@ void testPersistentSocketFailure(HTTP::Client *cl) {
|
||||
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 );
|
||||
@@ -909,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");
|
||||
@@ -942,6 +972,8 @@ int main(int argc, char* argv[])
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -619,6 +619,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) {
|
||||
@@ -694,8 +696,8 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -953,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());
|
||||
|
||||
@@ -1333,25 +1333,25 @@ 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -722,12 +724,6 @@ 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 (aCat->isUserEnabled() &&
|
||||
(aReason == Delegate::STATUS_REFRESHED) &&
|
||||
(catIt == d->catalogs.end()))
|
||||
@@ -761,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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
#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
|
||||
@@ -928,7 +928,8 @@ 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;
|
||||
}
|
||||
@@ -936,7 +937,9 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
if (fileName.empty())
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could not locate shader" << shaderName);
|
||||
|
||||
if (!compositorEnabled) {
|
||||
reportError("Missing shader", shaderName);
|
||||
}
|
||||
|
||||
throw BuilderException(string("couldn't find shader ") +
|
||||
shaderName);
|
||||
@@ -1481,10 +1484,8 @@ 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);
|
||||
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false))
|
||||
mergeSchemesFallbacks(this, options);
|
||||
|
||||
|
||||
@@ -273,11 +273,19 @@ 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::bad_alloc& ba) {
|
||||
SG_LOG(SG_GL, SG_ALERT, "Bad allocation loading:" << imageName);
|
||||
// todo: report low memory warning
|
||||
return false;
|
||||
}
|
||||
|
||||
options->setLoadOriginHint(origLOH);
|
||||
osg::ref_ptr<osg::Image> image;
|
||||
if (result.success())
|
||||
|
||||
@@ -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++ ) {
|
||||
|
||||
@@ -393,6 +393,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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -1135,16 +1135,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);
|
||||
}
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
#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>
|
||||
@@ -48,67 +48,250 @@
|
||||
|
||||
#include <simgear/scene/model/animation.hxx>
|
||||
|
||||
using ParticleSystemRef = osg::ref_ptr<osgParticle::ParticleSystem>;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class ParticlesGlobalManager::ParticlesGlobalManagerPrivate : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
ParticlesGlobalManagerPrivate() : _updater(new osgParticle::ParticleSystemUpdater),
|
||||
_commonGeode(new osg::Geode)
|
||||
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
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
_enabled = !_enabledNode || _enabledNode->getBoolValue();
|
||||
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
const auto q = SGQuatd::fromLonLatDeg(_longitudeNode->getFloatValue(), _latitudeNode->getFloatValue());
|
||||
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);
|
||||
}
|
||||
|
||||
// only call this with the lock held!
|
||||
osg::Group* internalGetCommonRoot()
|
||||
{
|
||||
if (!_commonRoot.valid()) {
|
||||
SG_LOG(SG_PARTICLES, SG_DEBUG, "Particle common root called.");
|
||||
_commonRoot = new osg::Group;
|
||||
_commonRoot->setName("common particle system root");
|
||||
_commonGeode->setName("common particle system geode");
|
||||
_commonRoot->addChild(_commonGeode);
|
||||
_commonRoot->addChild(_updater);
|
||||
_commonRoot->setNodeMask(~simgear::MODELLIGHT_BIT);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return _commonRoot.get();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
std::mutex _lock;
|
||||
bool _frozen = false;
|
||||
osg::ref_ptr<osgParticle::ParticleSystemUpdater> _updater;
|
||||
osg::ref_ptr<osg::Group> _commonRoot;
|
||||
osg::ref_ptr<osg::Geode> _commonGeode;
|
||||
osg::Vec3 _wind;
|
||||
bool _globalCallbackRegistered = false;
|
||||
bool _enabled = true;
|
||||
osg::Vec3 _gravity;
|
||||
// osg::Vec3 _localWind;
|
||||
|
||||
SGConstPropertyNode_ptr _enabledNode;
|
||||
SGConstPropertyNode_ptr _longitudeNode, _latitudeNode;
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -132,14 +315,6 @@ ParticlesGlobalManager::ParticlesGlobalManager() : d(new ParticlesGlobalManagerP
|
||||
{
|
||||
}
|
||||
|
||||
ParticlesGlobalManager::~ParticlesGlobalManager()
|
||||
{
|
||||
if (d->_globalCallbackRegistered) {
|
||||
// is this actually necessary? possibly not
|
||||
d->_updater->setUpdateCallback(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool ParticlesGlobalManager::isEnabled() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
@@ -728,23 +903,10 @@ osg::ref_ptr<osg::Group> ParticlesGlobalManager::appendParticles(const SGPropert
|
||||
emitter->setUpdateCallback(callback.get());
|
||||
}
|
||||
|
||||
// touch shared data now (and not before)
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_updater->addParticleSystem(particleSys);
|
||||
|
||||
if (attach != "local") {
|
||||
d->internalGetCommonRoot()->addChild(callback()->particleFrame);
|
||||
}
|
||||
|
||||
if (!d->_globalCallbackRegistered) {
|
||||
SG_LOG(SG_PARTICLES, SG_INFO, "Registering global particles callback");
|
||||
d->_globalCallbackRegistered = true;
|
||||
d->_longitudeNode = modelRoot->getNode("/position/longitude-deg", true);
|
||||
d->_latitudeNode = modelRoot->getNode("/position/latitude-deg", true);
|
||||
d->_updater->setUpdateCallback(d.get());
|
||||
}
|
||||
if (attach == "local") {
|
||||
d->registerNewLocalParticleSystem(align, particleSys);
|
||||
} else {
|
||||
d->registerNewWorldParticleSystem(align, particleSys, callback()->particleFrame);
|
||||
}
|
||||
|
||||
return align;
|
||||
|
||||
@@ -41,7 +41,6 @@ class NodeVisitor;
|
||||
namespace osgParticle
|
||||
{
|
||||
class ParticleSystem;
|
||||
class ParticleSystemUpdater;
|
||||
}
|
||||
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
@@ -151,7 +150,7 @@ protected:
|
||||
class ParticlesGlobalManager
|
||||
{
|
||||
public:
|
||||
~ParticlesGlobalManager();
|
||||
~ParticlesGlobalManager() = default;
|
||||
|
||||
static ParticlesGlobalManager* instance();
|
||||
static void clear();
|
||||
@@ -161,14 +160,18 @@ public:
|
||||
osg::ref_ptr<osg::Group> appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
|
||||
|
||||
osg::Group* getCommonRoot();
|
||||
|
||||
osg::Geode* getCommonGeode();
|
||||
|
||||
osgParticle::ParticleSystemUpdater* getPSU();
|
||||
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);
|
||||
|
||||
/**
|
||||
@@ -185,6 +188,8 @@ private:
|
||||
ParticlesGlobalManager();
|
||||
|
||||
class ParticlesGlobalManagerPrivate;
|
||||
class UpdaterCallback;
|
||||
class RegistrationCallback;
|
||||
|
||||
// because Private inherits NodeCallback, we need to own it
|
||||
// via an osg::ref_ptr
|
||||
|
||||
@@ -294,7 +294,7 @@ ReaderWriterSPT::createTree(const BucketBox& bucketBox, const LocalOptions& opti
|
||||
osg::ref_ptr<osg::Node>
|
||||
ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const
|
||||
{
|
||||
osg::PagedLOD* pagedLOD = new osg::PagedLOD;
|
||||
osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD;
|
||||
|
||||
pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
|
||||
SGSpheref sphere = bucketBox.getBoundingSphere();
|
||||
|
||||
@@ -212,7 +212,8 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
|
||||
quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end());
|
||||
osg::ref_ptr<osg::Group> group = quadtree.getRoot();
|
||||
group->setName("STG-group-A");
|
||||
string group_name = string("STG-group-A ").append(_bucket.gen_index_str());
|
||||
group->setName(group_name);
|
||||
group->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center());
|
||||
@@ -586,10 +587,12 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
{
|
||||
osg::ref_ptr<SGReaderWriterOptions> options;
|
||||
options = SGReaderWriterOptions::copyOrCreate(opt);
|
||||
float pagedLODExpiry = atoi(options->getPluginStringData("SimGear::PAGED_LOD_EXPIRY").c_str());
|
||||
|
||||
osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
|
||||
terrainGroup->setDataVariance(osg::Object::STATIC);
|
||||
terrainGroup->setName("terrain");
|
||||
std::string terrain_name = string("terrain ").append(bucket.gen_index_str());
|
||||
terrainGroup->setName(terrain_name);
|
||||
|
||||
if (_foundBase) {
|
||||
for (auto stgObject : _objectList) {
|
||||
@@ -637,11 +640,13 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
} else {
|
||||
osg::PagedLOD* pagedLOD = new osg::PagedLOD;
|
||||
pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
|
||||
pagedLOD->setName("pagedObjectLOD");
|
||||
std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str());
|
||||
pagedLOD->setName(name);
|
||||
|
||||
// This should be visible in any case.
|
||||
// If this is replaced by some lower level of detail, the parent LOD node handles this.
|
||||
pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max());
|
||||
pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry);
|
||||
|
||||
// we just need to know about the read file callback that itself holds the data
|
||||
osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
|
||||
@@ -658,10 +663,11 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
|
||||
// Objects may end up displayed up to 2x the object range.
|
||||
pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough);
|
||||
pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry);
|
||||
pagedLOD->setRadius(SG_TILE_RADIUS);
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough));
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Radius: " << SG_TILE_RADIUS);
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough));
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS);
|
||||
return pagedLOD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,6 +441,10 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
if (hash_pos != std::string::npos)
|
||||
line.resize(hash_pos);
|
||||
|
||||
if (line.empty()) {
|
||||
continue; // skip blank lines
|
||||
}
|
||||
|
||||
// and process further
|
||||
std::stringstream in(line);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/model/BoundingVolumeBuildVisitor.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/VectorArrayAdapter.hxx>
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
@@ -340,5 +341,10 @@ osg::Node* SGOceanTile(const SGBucket& b, SGMaterialLib *matlib, int latPoints,
|
||||
transform->addChild(geode);
|
||||
transform->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) );
|
||||
|
||||
// Create a BVH at this point. This is normally provided by the file loader, but as we create the
|
||||
// geometry programmatically, no file loader is involved.
|
||||
BoundingVolumeBuildVisitor bvhBuilder(false);
|
||||
transform->accept(bvhBuilder);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
@@ -109,13 +109,13 @@ bool hasWhitespace(string path)
|
||||
class SyncItem
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
Stop = 0, ///< special item indicating to stop the SVNThread
|
||||
enum Type {
|
||||
Stop = 0, ///< special item indicating to stop the SVNThread
|
||||
Tile,
|
||||
AirportData,
|
||||
SharedModels,
|
||||
AIData
|
||||
AIData,
|
||||
OSMTile ///< OSm2City per-Tile data
|
||||
};
|
||||
|
||||
enum Status
|
||||
@@ -165,13 +165,16 @@ public:
|
||||
SGTimeStamp stamp;
|
||||
bool busy = false; ///< is the slot working or idle
|
||||
unsigned int pendingKBytes = 0;
|
||||
unsigned int pendingExtractKBytes = 0;
|
||||
unsigned int nextWarnTimeout = 0;
|
||||
};
|
||||
|
||||
static const int SYNC_SLOT_TILES = 0; ///< Terrain and Objects sync
|
||||
static const int SYNC_SLOT_SHARED_DATA = 1; /// shared Models and Airport data
|
||||
static const int SYNC_SLOT_AI_DATA = 2; /// AI traffic and models
|
||||
static const unsigned int NUM_SYNC_SLOTS = 3;
|
||||
static const int SYNC_SLOT_OSM_TILE_DATA = 3;
|
||||
|
||||
static const unsigned int NUM_SYNC_SLOTS = 4;
|
||||
|
||||
/**
|
||||
* @brief translate a sync item type into one of the available slots.
|
||||
@@ -186,7 +189,8 @@ static unsigned int syncSlotForType(SyncItem::Type ty)
|
||||
return SYNC_SLOT_SHARED_DATA;
|
||||
case SyncItem::AIData:
|
||||
return SYNC_SLOT_AI_DATA;
|
||||
|
||||
case SyncItem::OSMTile:
|
||||
return SYNC_SLOT_OSM_TILE_DATA;
|
||||
default:
|
||||
return SYNC_SLOT_SHARED_DATA;
|
||||
}
|
||||
@@ -194,18 +198,18 @@ static unsigned int syncSlotForType(SyncItem::Type ty)
|
||||
|
||||
struct TerrasyncThreadState
|
||||
{
|
||||
TerrasyncThreadState() :
|
||||
_busy(false),
|
||||
_stalled(false),
|
||||
_hasServer(false),
|
||||
_fail_count(0),
|
||||
_updated_tile_count(0),
|
||||
_success_count(0),
|
||||
_consecutive_errors(0),
|
||||
_cache_hits(0),
|
||||
_transfer_rate(0),
|
||||
_total_kb_downloaded(0),
|
||||
_totalKbPending(0)
|
||||
TerrasyncThreadState() : _busy(false),
|
||||
_stalled(false),
|
||||
_hasServer(false),
|
||||
_fail_count(0),
|
||||
_updated_tile_count(0),
|
||||
_success_count(0),
|
||||
_consecutive_errors(0),
|
||||
_cache_hits(0),
|
||||
_transfer_rate(0),
|
||||
_total_kb_downloaded(0),
|
||||
_totalKbPending(0),
|
||||
_extractTotalKbPending(0)
|
||||
{}
|
||||
|
||||
bool _busy;
|
||||
@@ -220,6 +224,7 @@ struct TerrasyncThreadState
|
||||
// kbytes, not bytes, because bytes might overflow 2^31
|
||||
int _total_kb_downloaded;
|
||||
unsigned int _totalKbPending;
|
||||
unsigned int _extractTotalKbPending;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -275,9 +280,10 @@ public:
|
||||
|
||||
SyncItem getNewTile() { return _freshTiles.pop_front();}
|
||||
|
||||
void setHTTPServer(const std::string& server)
|
||||
void setHTTPServer(const std::string& server, const std::string& osmServer)
|
||||
{
|
||||
_httpServer = stripPath(server);
|
||||
_osmCityServer = stripPath(osmServer);
|
||||
_isAutomaticServer = (server == "automatic");
|
||||
}
|
||||
|
||||
@@ -296,6 +302,11 @@ public:
|
||||
_sceneryVersion = simgear::strutils::strip(sceneryVersion);
|
||||
}
|
||||
|
||||
void setOSMCityVersion(const std::string& osmCityVersion)
|
||||
{
|
||||
_osmCityService = osmCityVersion;
|
||||
}
|
||||
|
||||
void setLocalDir(string dir) { _local_dir = stripPath(dir);}
|
||||
string getLocalDir() { return _local_dir;}
|
||||
|
||||
@@ -322,10 +333,12 @@ public:
|
||||
void setCachePath(const SGPath &p) { _persistentCachePath = p; }
|
||||
|
||||
private:
|
||||
void incrementCacheHits()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_stateLock);
|
||||
_state._cache_hits++;
|
||||
std::string dnsSelectServerForService(const std::string& service);
|
||||
|
||||
void incrementCacheHits()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_stateLock);
|
||||
_state._cache_hits++;
|
||||
}
|
||||
|
||||
virtual void run();
|
||||
@@ -334,6 +347,10 @@ public:
|
||||
void runInternal();
|
||||
void updateSyncSlot(SyncSlot& slot);
|
||||
|
||||
bool beginSyncAirports(SyncSlot& slot);
|
||||
bool beginSyncTile(SyncSlot& slot);
|
||||
bool beginNormalSync(SyncSlot& slot);
|
||||
|
||||
void drainWaitingTiles();
|
||||
|
||||
// commond helpers between both internal and external models
|
||||
@@ -359,6 +376,9 @@ public:
|
||||
string _local_dir;
|
||||
SGPath _persistentCachePath;
|
||||
string _httpServer;
|
||||
string _osmCityServer;
|
||||
string _osmCityService = "o2c";
|
||||
|
||||
bool _isAutomaticServer;
|
||||
SGPath _installRoot;
|
||||
string _sceneryVersion;
|
||||
@@ -470,8 +490,19 @@ bool SGTerraSync::WorkerThread::findServer()
|
||||
{
|
||||
if ( false == _isAutomaticServer ) return true;
|
||||
|
||||
_httpServer = dnsSelectServerForService(MakeQService(_protocol, _sceneryVersion));
|
||||
|
||||
if (!_osmCityService.empty()) {
|
||||
_osmCityServer = dnsSelectServerForService(_osmCityService);
|
||||
}
|
||||
|
||||
return !_httpServer.empty();
|
||||
}
|
||||
|
||||
std::string SGTerraSync::WorkerThread::dnsSelectServerForService(const std::string& service)
|
||||
{
|
||||
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(_dnsdn);
|
||||
naptrRequest->qservice = MakeQService(_protocol, _sceneryVersion);
|
||||
naptrRequest->qservice = service;
|
||||
|
||||
naptrRequest->qflags = "U";
|
||||
DNS::Request_ptr r(naptrRequest);
|
||||
@@ -479,14 +510,15 @@ bool SGTerraSync::WorkerThread::findServer()
|
||||
DNS::Client dnsClient;
|
||||
dnsClient.makeRequest(r);
|
||||
SG_LOG(SG_TERRASYNC,SG_DEBUG,"DNS NAPTR query for '" << _dnsdn << "' '" << naptrRequest->qservice << "'" );
|
||||
while( !r->isComplete() && !r->isTimeout() )
|
||||
dnsClient.update(0);
|
||||
while (!r->isComplete() && !r->isTimeout()) {
|
||||
dnsClient.update(0);
|
||||
}
|
||||
|
||||
if( naptrRequest->entries.empty() ) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Warning: no DNS entry found for '" << _dnsdn << "' '" << naptrRequest->qservice << "'" );
|
||||
_httpServer = "";
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
// walk through responses, they are ordered by 1. order and 2. preference
|
||||
// For now, only take entries with lowest order
|
||||
// TODO: try all available servers in the order given by preferenc and order
|
||||
@@ -494,38 +526,33 @@ bool SGTerraSync::WorkerThread::findServer()
|
||||
|
||||
// get all servers with this order and the same (for now only lowest preference)
|
||||
DNS::NAPTRRequest::NAPTR_list availableServers;
|
||||
for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin();
|
||||
it != naptrRequest->entries.end();
|
||||
++it ) {
|
||||
|
||||
if( (*it)->order != order )
|
||||
for (const auto& entry : naptrRequest->entries) {
|
||||
if (entry->order != order)
|
||||
continue;
|
||||
|
||||
string regex = (*it)->regexp;
|
||||
if( false == simgear::strutils::starts_with( (*it)->regexp, "!^.*$!" ) ) {
|
||||
SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
|
||||
const string regex = entry->regexp;
|
||||
if (false == simgear::strutils::starts_with(regex, "!^.*$!")) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "ignoring unsupported regexp: " << regex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if( false == simgear::strutils::ends_with( (*it)->regexp, "!" ) ) {
|
||||
SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
|
||||
if (false == simgear::strutils::ends_with(regex, "!")) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "ignoring unsupported regexp: " << regex);
|
||||
continue;
|
||||
}
|
||||
|
||||
// always use first entry
|
||||
if( availableServers.empty() || (*it)->preference == availableServers[0]->preference) {
|
||||
SG_LOG(SG_TERRASYNC,SG_DEBUG, "available server regexp: " << (*it)->regexp );
|
||||
availableServers.push_back( *it );
|
||||
if (availableServers.empty() || entry->preference == availableServers[0]->preference) {
|
||||
SG_LOG(SG_TERRASYNC, SG_DEBUG, "available server regexp: " << regex);
|
||||
availableServers.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// now pick a random entry from the available servers
|
||||
DNS::NAPTRRequest::NAPTR_list::size_type idx = sg_random() * availableServers.size();
|
||||
_httpServer = availableServers[idx]->regexp;
|
||||
_httpServer = _httpServer.substr( 6, _httpServer.length()-7 ); // strip search pattern and separators
|
||||
|
||||
SG_LOG(SG_TERRASYNC,SG_INFO, "picking entry # " << idx << ", server is " << _httpServer );
|
||||
return true;
|
||||
auto idx = static_cast<int>(sg_random() * availableServers.size());
|
||||
const auto server = availableServers.at(idx)->regexp;
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "picking entry # " << idx << ", server is " << server.substr(6, server.length() - 7););
|
||||
return server.substr(6, server.length() - 7);
|
||||
}
|
||||
|
||||
void SGTerraSync::WorkerThread::run()
|
||||
@@ -547,6 +574,7 @@ void SGTerraSync::WorkerThread::run()
|
||||
void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
|
||||
{
|
||||
if (slot.repository.get()) {
|
||||
slot.repository->process();
|
||||
if (slot.repository->isDoingSync()) {
|
||||
#if 1
|
||||
if (slot.stamp.elapsedMSec() > (int)slot.nextWarnTimeout) {
|
||||
@@ -557,6 +585,7 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
|
||||
#endif
|
||||
// convert bytes to kbytes here
|
||||
slot.pendingKBytes = (slot.repository->bytesToDownload() >> 10);
|
||||
slot.pendingExtractKBytes = (slot.repository->bytesToExtract() >> 10);
|
||||
return; // easy, still working
|
||||
}
|
||||
|
||||
@@ -564,9 +593,21 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
|
||||
HTTPRepository::ResultCode res = slot.repository->failure();
|
||||
|
||||
if (res == HTTPRepository::REPO_ERROR_NOT_FOUND) {
|
||||
// not founds should never happen any more (unless the server-
|
||||
// side data is incorrect), since we now check top-down that
|
||||
// a 1x1 dir exists or not.
|
||||
notFound(slot.currentItem);
|
||||
} else if (res != HTTPRepository::REPO_NO_ERROR) {
|
||||
fail(slot.currentItem);
|
||||
|
||||
// in case the Airports_archive download fails, create the
|
||||
// directory, so that next sync, we do a manual sync
|
||||
if ((slot.currentItem._type == SyncItem::AirportData) && slot.isNewDirectory) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Failed to download Airports_archive, will download discrete files next time");
|
||||
simgear::Dir d(_local_dir + "/Airports");
|
||||
d.create(0755);
|
||||
_completedTiles.erase(slot.currentItem._dir);
|
||||
}
|
||||
} else {
|
||||
updated(slot.currentItem, slot.isNewDirectory);
|
||||
SG_LOG(SG_TERRASYNC, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " finished ("
|
||||
@@ -577,6 +618,7 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
|
||||
slot.busy = false;
|
||||
slot.repository.reset();
|
||||
slot.pendingKBytes = 0;
|
||||
slot.pendingExtractKBytes = 0;
|
||||
slot.currentItem = {};
|
||||
}
|
||||
|
||||
@@ -588,47 +630,25 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
|
||||
SGPath path(_local_dir);
|
||||
path.append(slot.currentItem._dir);
|
||||
slot.isNewDirectory = !path.exists();
|
||||
if (slot.isNewDirectory) {
|
||||
int rc = path.create_dir( 0755 );
|
||||
if (rc) {
|
||||
SG_LOG(SG_TERRASYNC,SG_ALERT,
|
||||
"Cannot create directory '" << path << "', return code = " << rc );
|
||||
fail(slot.currentItem);
|
||||
return;
|
||||
}
|
||||
} // of creating directory step
|
||||
|
||||
// optimise initial Airport download
|
||||
if (slot.isNewDirectory &&
|
||||
(slot.currentItem._type == SyncItem::AirportData)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "doing Airports download via tarball");
|
||||
|
||||
// we want to sync the 'root' TerraSync dir, but not all of it, just
|
||||
// the Airports_archive.tar.gz file so we use our TerraSync local root
|
||||
// as the path (since the archive will add Airports/)
|
||||
slot.repository.reset(new HTTPRepository(_local_dir, &_http));
|
||||
slot.repository->setBaseUrl(_httpServer + "/");
|
||||
|
||||
// filter callback to *only* sync the Airport_archive tarball,
|
||||
// and ensure no other contents are touched
|
||||
auto f = [](const HTTPRepository::SyncItem &item) {
|
||||
if (!item.directory.empty())
|
||||
return false;
|
||||
return (item.filename.find("Airports_archive.") == 0);
|
||||
};
|
||||
|
||||
slot.repository->setFilter(f);
|
||||
const auto type = slot.currentItem._type;
|
||||
|
||||
bool ok = false;
|
||||
if (type == SyncItem::AirportData) {
|
||||
ok = beginSyncAirports(slot);
|
||||
} else if (type == SyncItem::OSMTile) {
|
||||
ok = beginSyncTile(slot);
|
||||
} else if (type == SyncItem::Tile) {
|
||||
ok = beginSyncTile(slot);
|
||||
} else {
|
||||
slot.repository.reset(new HTTPRepository(path, &_http));
|
||||
slot.repository->setBaseUrl(_httpServer + "/" +
|
||||
slot.currentItem._dir);
|
||||
ok = beginNormalSync(slot);
|
||||
}
|
||||
|
||||
if (_installRoot.exists()) {
|
||||
SGPath p = _installRoot;
|
||||
p.append(slot.currentItem._dir);
|
||||
slot.repository->setInstalledCopyPath(p);
|
||||
if (!ok) {
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.currentItem._dir << " failed to start");
|
||||
fail(slot.currentItem);
|
||||
slot.busy = false;
|
||||
slot.repository.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -645,12 +665,119 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
|
||||
slot.nextWarnTimeout = 30 * 1000;
|
||||
slot.stamp.stamp();
|
||||
slot.busy = true;
|
||||
slot.pendingKBytes = slot.repository->bytesToDownload();
|
||||
slot.pendingKBytes = slot.repository->bytesToDownload() >> 10;
|
||||
slot.pendingExtractKBytes = slot.repository->bytesToExtract() >> 10;
|
||||
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << " started, queue size is " << slot.queue.size());
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << ":" << slot.currentItem._dir << " started, queue size is " << slot.queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool SGTerraSync::WorkerThread::beginSyncAirports(SyncSlot& slot)
|
||||
{
|
||||
if (!slot.isNewDirectory) {
|
||||
return beginNormalSync(slot);
|
||||
}
|
||||
|
||||
SG_LOG(SG_TERRASYNC, SG_INFO, "doing Airports download via tarball");
|
||||
|
||||
// we want to sync the 'root' TerraSync dir, but not all of it, just
|
||||
// the Airports_archive.tar.gz file so we use our TerraSync local root
|
||||
// as the path (since the archive will add Airports/)
|
||||
slot.repository.reset(new HTTPRepository(_local_dir, &_http));
|
||||
slot.repository->setBaseUrl(_httpServer);
|
||||
|
||||
// filter callback to *only* sync the Airport_archive tarball,
|
||||
// and ensure no other contents are touched
|
||||
auto f = [](const HTTPRepository::SyncItem& item) {
|
||||
if (!item.directory.empty())
|
||||
return false;
|
||||
return (item.filename.find("Airports_archive.") == 0);
|
||||
};
|
||||
|
||||
slot.repository->setFilter(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
|
||||
{
|
||||
// avoid 404 requests by doing a sync which excludes all paths
|
||||
// except our tile path. In the case of a missing 1x1 tile, we will
|
||||
// stop becuase all directories are filtered out, which is what we want
|
||||
|
||||
auto comps = strutils::split(slot.currentItem._dir, "/");
|
||||
if (comps.size() != 3) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Bad tile path:" << slot.currentItem._dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto tileCategory = comps.front();
|
||||
const auto tenByTenDir = comps.at(1);
|
||||
const auto oneByOneDir = comps.at(2);
|
||||
|
||||
const auto path = SGPath::fromUtf8(_local_dir) / tileCategory;
|
||||
slot.repository.reset(new HTTPRepository(path, &_http));
|
||||
|
||||
if (slot.currentItem._type == SyncItem::OSMTile) {
|
||||
if (_osmCityServer.empty()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "No OSM2City server defined for:" << slot.currentItem._dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
slot.repository->setBaseUrl(_osmCityServer + "/" + tileCategory);
|
||||
} else {
|
||||
slot.repository->setBaseUrl(_httpServer + "/" + tileCategory);
|
||||
}
|
||||
|
||||
if (_installRoot.exists()) {
|
||||
SGPath p = _installRoot / tileCategory;
|
||||
slot.repository->setInstalledCopyPath(p);
|
||||
}
|
||||
|
||||
const auto dirPrefix = tenByTenDir + "/" + oneByOneDir;
|
||||
using simgear::strutils::starts_with;
|
||||
|
||||
// filter callback to *only* sync the 1x1 dir we want, if it exists
|
||||
// if doesn't, we'll simply stop, which is what we want
|
||||
auto f = [tenByTenDir, oneByOneDir, dirPrefix](const HTTPRepository::SyncItem& item) {
|
||||
// only allow the specific 10x10 and 1x1 dirs we want
|
||||
if (item.directory.empty()) {
|
||||
return item.filename == tenByTenDir;
|
||||
} else if (item.directory == tenByTenDir) {
|
||||
// allow 10x10 dir to contain 1x1_dir.tgz/.zip and still be accepted
|
||||
// this does mean we'd also download oneByOneDir_foobar but that
|
||||
// doesn't seem unreasonable either
|
||||
return starts_with(item.filename, oneByOneDir);
|
||||
}
|
||||
|
||||
// allow arbitrary children below dirPrefix, including sub-dirs
|
||||
if (item.directory.find(dirPrefix) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "Tile sync: saw weird path:" << item.directory << " file " << item.filename);
|
||||
return false;
|
||||
};
|
||||
|
||||
slot.repository->setFilter(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SGTerraSync::WorkerThread::beginNormalSync(SyncSlot& slot)
|
||||
{
|
||||
SGPath path(_local_dir);
|
||||
path.append(slot.currentItem._dir);
|
||||
slot.repository.reset(new HTTPRepository(path, &_http));
|
||||
slot.repository->setBaseUrl(_httpServer + "/" + slot.currentItem._dir);
|
||||
|
||||
if (_installRoot.exists()) {
|
||||
SGPath p = _installRoot;
|
||||
p.append(slot.currentItem._dir);
|
||||
slot.repository->setInstalledCopyPath(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SGTerraSync::WorkerThread::runInternal()
|
||||
{
|
||||
while (!_stop) {
|
||||
@@ -691,20 +818,24 @@ void SGTerraSync::WorkerThread::runInternal()
|
||||
|
||||
bool anySlotBusy = false;
|
||||
unsigned int newPendingCount = 0;
|
||||
unsigned int newExtractCount = 0; // how much is left to extract
|
||||
|
||||
// update each sync slot in turn
|
||||
for (unsigned int slot=0; slot < NUM_SYNC_SLOTS; ++slot) {
|
||||
updateSyncSlot(_syncSlots[slot]);
|
||||
newPendingCount += _syncSlots[slot].pendingKBytes;
|
||||
newExtractCount += _syncSlots[slot].pendingExtractKBytes;
|
||||
anySlotBusy |= _syncSlots[slot].busy;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_stateLock);
|
||||
_state._totalKbPending = newPendingCount; // approximately atomic update
|
||||
_state._extractTotalKbPending = newExtractCount;
|
||||
_state._busy = anySlotBusy;
|
||||
}
|
||||
|
||||
|
||||
if (!anySlotBusy) {
|
||||
// wait on the blocking deque here, otherwise we spin
|
||||
// the loop very fast, since _http::update with no connections
|
||||
@@ -740,12 +871,19 @@ void SGTerraSync::WorkerThread::fail(SyncItem failedItem)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_stateLock);
|
||||
time_t now = time(0);
|
||||
_state._consecutive_errors++;
|
||||
_state._fail_count++;
|
||||
|
||||
if (_osmCityServer.empty() && (failedItem._type == SyncItem::OSMTile)) {
|
||||
// don't count these as errors, otherwise normla sync will keep
|
||||
// being abandoned
|
||||
} else {
|
||||
_state._consecutive_errors++;
|
||||
_state._fail_count++;
|
||||
}
|
||||
|
||||
failedItem._status = SyncItem::Failed;
|
||||
_freshTiles.push_back(failedItem);
|
||||
// not we also end up here for partial syncs
|
||||
SG_LOG(SG_TERRASYNC,SG_INFO,
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN,
|
||||
"Failed to sync'" << failedItem._dir << "'");
|
||||
_completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
|
||||
}
|
||||
@@ -757,6 +895,8 @@ void SGTerraSync::WorkerThread::notFound(SyncItem item)
|
||||
// we don't spam the server with lookups for models that don't
|
||||
// exist
|
||||
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "Not found for: '" << item._dir << "'");
|
||||
|
||||
time_t now = time(0);
|
||||
item._status = SyncItem::NotFound;
|
||||
_freshTiles.push_back(item);
|
||||
@@ -969,8 +1109,11 @@ void SGTerraSync::reinit()
|
||||
if (enabled)
|
||||
{
|
||||
_availableNode->setBoolValue(true);
|
||||
_workerThread->setHTTPServer( _terraRoot->getStringValue("http-server","automatic") );
|
||||
_workerThread->setHTTPServer(
|
||||
_terraRoot->getStringValue("http-server", "automatic"),
|
||||
_terraRoot->getStringValue("osm2city-server", ""));
|
||||
_workerThread->setSceneryVersion( _terraRoot->getStringValue("scenery-version","ws20") );
|
||||
_workerThread->setOSMCityVersion(_terraRoot->getStringValue("osm2city-version", "o2c"));
|
||||
_workerThread->setProtocol( _terraRoot->getStringValue("protocol","") );
|
||||
#if 1
|
||||
// leave it hardcoded for now, not sure about the security implications for now
|
||||
@@ -981,6 +1124,9 @@ void SGTerraSync::reinit()
|
||||
|
||||
SGPath sceneryRoot{_terraRoot->getStringValue("scenery-dir", "")};
|
||||
_workerThread->setLocalDir(sceneryRoot.utf8Str());
|
||||
if (sceneryRoot.exists()) {
|
||||
writeWarningFile(sceneryRoot);
|
||||
}
|
||||
|
||||
SGPath installPath(_terraRoot->getStringValue("installation-dir"));
|
||||
_workerThread->setInstalledDir(installPath);
|
||||
@@ -1029,6 +1175,7 @@ void SGTerraSync::bind()
|
||||
_transferRateBytesSecNode = _terraRoot->getNode("transfer-rate-bytes-sec", true);
|
||||
_pendingKbytesNode = _terraRoot->getNode("pending-kbytes", true);
|
||||
_downloadedKBtesNode = _terraRoot->getNode("downloaded-kbytes", true);
|
||||
_extractPendingKbytesNode = _terraRoot->getNode("extract-pending-kbytes", true);
|
||||
_enabledNode = _terraRoot->getNode("enabled", true);
|
||||
_availableNode = _terraRoot->getNode("available", true);
|
||||
_maxErrorsNode = _terraRoot->getNode("max-errors", true);
|
||||
@@ -1054,6 +1201,7 @@ void SGTerraSync::unbind()
|
||||
_transferRateBytesSecNode.clear();
|
||||
_pendingKbytesNode.clear();
|
||||
_downloadedKBtesNode.clear();
|
||||
_extractPendingKbytesNode.clear();
|
||||
_enabledNode.clear();
|
||||
_availableNode.clear();
|
||||
_maxErrorsNode.clear();
|
||||
@@ -1090,6 +1238,7 @@ void SGTerraSync::update(double)
|
||||
_transferRateBytesSecNode->setIntValue(copiedState._transfer_rate);
|
||||
_pendingKbytesNode->setIntValue(copiedState._totalKbPending);
|
||||
_downloadedKBtesNode->setIntValue(copiedState._total_kb_downloaded);
|
||||
_extractPendingKbytesNode->setIntValue(copiedState._extractTotalKbPending);
|
||||
|
||||
_stalledNode->setBoolValue(_workerThread->isStalled());
|
||||
_activeNode->setBoolValue(worker_running);
|
||||
@@ -1139,6 +1288,10 @@ string_list SGTerraSync::getSceneryPathSuffixes() const
|
||||
return scenerySuffixes;
|
||||
}
|
||||
|
||||
bool isOSMSuffix(const std::string& suffix)
|
||||
{
|
||||
return (suffix == "Buildings") || (suffix == "Roads") || (suffix == "Pylons") || (suffix == "Details");
|
||||
}
|
||||
|
||||
void SGTerraSync::syncAreaByPath(const std::string& aPath)
|
||||
{
|
||||
@@ -1151,8 +1304,7 @@ void SGTerraSync::syncAreaByPath(const std::string& aPath)
|
||||
if (_workerThread->isDirActive(dir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SyncItem w(dir, SyncItem::Tile);
|
||||
SyncItem w(dir, isOSMSuffix(suffix) ? SyncItem::OSMTile : SyncItem::Tile);
|
||||
_workerThread->request( w );
|
||||
}
|
||||
}
|
||||
@@ -1171,6 +1323,11 @@ bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
|
||||
}
|
||||
|
||||
for (const auto& suffix : getSceneryPathSuffixes()) {
|
||||
// don't wait on OSM dirs, even if enabled
|
||||
if (isOSMSuffix(suffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto s = suffix + "/" + sceneryDir;
|
||||
if (_workerThread->isDirActive(s)) {
|
||||
return true;
|
||||
@@ -1208,6 +1365,23 @@ void SGTerraSync::reposition()
|
||||
// stub, remove
|
||||
}
|
||||
|
||||
void SGTerraSync::writeWarningFile(const SGPath& sceneryDir)
|
||||
{
|
||||
SGPath p = sceneryDir / "TerraSync-WARNING.txt";
|
||||
if (p.exists())
|
||||
return;
|
||||
|
||||
sg_ofstream os(p, std::ios::out | std::ios::trunc);
|
||||
os << "This folder is managed by FlightGear's download system.\n";
|
||||
os << "Any changes you make here or in sub-folders will be overwritten when TerraSync\n";
|
||||
os << "downloads updates.\n";
|
||||
os << "\n";
|
||||
os << "To use custom scenery or data with FlightGear, put it in a differnet location\n";
|
||||
os << "on your computer, then add the location using either the launcher 'Add-ons' page, or by\n";
|
||||
os << "passing '--fg-scenery=<location>' on the command line.";
|
||||
os << endl;
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<SGTerraSync> registrantSGTerraSync(
|
||||
|
||||
@@ -90,6 +90,9 @@ protected:
|
||||
|
||||
class WorkerThread;
|
||||
|
||||
private:
|
||||
void writeWarningFile(const SGPath& sceneryDir);
|
||||
|
||||
private:
|
||||
WorkerThread* _workerThread;
|
||||
SGPropertyNode_ptr _terraRoot;
|
||||
@@ -107,6 +110,7 @@ private:
|
||||
SGPropertyNode_ptr _transferRateBytesSecNode;
|
||||
SGPropertyNode_ptr _pendingKbytesNode;
|
||||
SGPropertyNode_ptr _downloadedKBtesNode;
|
||||
SGPropertyNode_ptr _extractPendingKbytesNode;
|
||||
SGPropertyNode_ptr _maxErrorsNode;
|
||||
|
||||
// we manually bind+init TerraSync during early startup
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
ORIGIN_EFFECTS,
|
||||
ORIGIN_EFFECTS_NORMALIZED,
|
||||
ORIGIN_SPLASH_SCREEN,
|
||||
ORIGIN_CANVAS,
|
||||
};
|
||||
|
||||
//SGReaderWriterOptions* cloneOptions(const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) const { return static_cast<SGReaderWriterOptions*>(clone(copyop)); }
|
||||
|
||||
86
simgear/structure/SGAction.cxx
Normal file
86
simgear/structure/SGAction.cxx
Normal file
@@ -0,0 +1,86 @@
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
set bindings for action seperate from defintion ?
|
||||
- in XML, especially aircraft XML
|
||||
|
||||
define actions from command / Nasal
|
||||
add behaviours from Nasal
|
||||
|
||||
define keymapping
|
||||
- manager of keybindings defined against actions?
|
||||
- for a toggle or enum, define behaviour
|
||||
- each key repeat cycles
|
||||
- alternate key to go the other way (G/shift-G)
|
||||
|
||||
release bindings for momentary actions:
|
||||
button up / key-up
|
||||
|
||||
send activate, release
|
||||
|
||||
send deactivate, release for 'alternate' action
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void SGAction::setValueExpression()
|
||||
{
|
||||
// watch all the properties
|
||||
}
|
||||
|
||||
void SGAction::setValueCondition()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void SGAction::updateProperties()
|
||||
{
|
||||
//
|
||||
_node->setBoolValue("enabled", isEnabled());
|
||||
switch (_type) {
|
||||
case Momentary:
|
||||
case Toggle:
|
||||
_node->setBoolValue("value", getValue());
|
||||
break;
|
||||
|
||||
case Enumerated:
|
||||
if (!_valueEnumeration.empty()) {
|
||||
// map to the string value
|
||||
_node->setStringValue("value", _valueEnumeration.at(getValue()));
|
||||
} else {
|
||||
// set as an integer
|
||||
_node->setIntValue("value", getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// set description
|
||||
}
|
||||
|
||||
bool SGAction::isEnabled()
|
||||
{
|
||||
if (_enableCondition) {
|
||||
|
||||
} else {
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
updateProperties();
|
||||
}
|
||||
|
||||
int SGAction::getValue()
|
||||
{
|
||||
if (type == Enumerated) {
|
||||
if (_valueExpression) {
|
||||
// invoke it
|
||||
}
|
||||
} else {
|
||||
if (_valueCondition) {
|
||||
return _valueCondition.test();
|
||||
}
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
// commands enable-action, disable-action
|
||||
@@ -441,7 +441,7 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
|
||||
std::string nm = stateDesc->getStringValue("name");
|
||||
|
||||
if (nm.empty()) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "No name found for state in branch " << path);
|
||||
SG_LOG(SG_GENERAL, SG_DEV_ALERT, "No name found for state in branch " << path);
|
||||
throw sg_exception("No name element in state");
|
||||
}
|
||||
|
||||
@@ -464,8 +464,8 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
|
||||
std::string target_id = tDesc->getStringValue("target");
|
||||
|
||||
if (nm.empty()) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "No name found for transition in branch " << path);
|
||||
throw sg_exception("No name element in transition");
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "No name found for transition in branch " << path);
|
||||
nm = "transition-to-" + target_id;
|
||||
}
|
||||
|
||||
if (target_id.empty()) {
|
||||
|
||||
@@ -19,66 +19,65 @@ static ThrowCallback static_callback;
|
||||
// Implementation of sg_location class.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_location::sg_location ()
|
||||
: _line(-1),
|
||||
_column(-1),
|
||||
_byte(-1)
|
||||
sg_location::sg_location() noexcept
|
||||
: _line(-1),
|
||||
_column(-1),
|
||||
_byte(-1)
|
||||
{
|
||||
_path[0] = '\0';
|
||||
}
|
||||
|
||||
sg_location::sg_location (const std::string& path, int line, int column)
|
||||
: _line(line),
|
||||
_column(column),
|
||||
_byte(-1)
|
||||
sg_location::sg_location(const std::string& path, int line, int column) noexcept
|
||||
: _line(line),
|
||||
_column(column),
|
||||
_byte(-1)
|
||||
{
|
||||
setPath(path.c_str());
|
||||
}
|
||||
|
||||
sg_location::sg_location (const SGPath& path, int line, int column)
|
||||
: _line(line),
|
||||
_column(column),
|
||||
_byte(-1)
|
||||
sg_location::sg_location(const SGPath& path, int line, int column) noexcept
|
||||
: _line(line),
|
||||
_column(column),
|
||||
_byte(-1)
|
||||
{
|
||||
setPath(path.utf8Str().c_str());
|
||||
}
|
||||
|
||||
sg_location::sg_location (const char* path, int line, int column)
|
||||
: _line(line),
|
||||
_column(column),
|
||||
_byte(-1)
|
||||
sg_location::sg_location(const char* path, int line, int column) noexcept
|
||||
: _line(line),
|
||||
_column(column),
|
||||
_byte(-1)
|
||||
{
|
||||
setPath(path);
|
||||
}
|
||||
|
||||
void sg_location::setPath(const char *path) {
|
||||
if (path) {
|
||||
strncpy(_path, path, max_path);
|
||||
_path[max_path -1] = '\0';
|
||||
} else {
|
||||
_path[0] = '\0';
|
||||
}
|
||||
void sg_location::setPath(const char* path) noexcept
|
||||
{
|
||||
if (path) {
|
||||
strncpy(_path, path, max_path);
|
||||
_path[max_path - 1] = '\0';
|
||||
} else {
|
||||
_path[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
const char *sg_location::getPath() const { return _path; }
|
||||
const char* sg_location::getPath() const noexcept { return _path; }
|
||||
|
||||
int sg_location::getLine() const { return _line; }
|
||||
int sg_location::getLine() const noexcept { return _line; }
|
||||
|
||||
int
|
||||
sg_location::getColumn () const
|
||||
int sg_location::getColumn() const noexcept
|
||||
{
|
||||
return _column;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sg_location::getByte () const
|
||||
int sg_location::getByte() const noexcept
|
||||
{
|
||||
return _byte;
|
||||
}
|
||||
|
||||
std::string
|
||||
sg_location::asString () const
|
||||
sg_location::asString() const noexcept
|
||||
{
|
||||
std::ostringstream out;
|
||||
if (_path[0]) {
|
||||
@@ -97,46 +96,48 @@ sg_location::asString () const
|
||||
return out.str();
|
||||
}
|
||||
|
||||
bool sg_location::isValid() const { return strlen(_path) > 0; }
|
||||
bool sg_location::isValid() const noexcept { return strlen(_path) > 0; }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of sg_throwable class.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_throwable::sg_throwable ()
|
||||
sg_throwable::sg_throwable() noexcept
|
||||
{
|
||||
_message[0] = '\0';
|
||||
_origin[0] = '\0';
|
||||
}
|
||||
|
||||
sg_throwable::sg_throwable(const char *message, const char *origin,
|
||||
const sg_location &loc) {
|
||||
setMessage(message);
|
||||
setOrigin(origin);
|
||||
|
||||
if (static_callback) {
|
||||
static_callback(_message, _origin, loc);
|
||||
}
|
||||
}
|
||||
|
||||
sg_throwable::~sg_throwable ()
|
||||
sg_throwable::sg_throwable(const char* message, const char* origin,
|
||||
const sg_location& loc, bool report) noexcept : _location(loc)
|
||||
{
|
||||
setMessage(message);
|
||||
setOrigin(origin);
|
||||
|
||||
if (static_callback && report) {
|
||||
static_callback(_message, _origin, loc);
|
||||
}
|
||||
}
|
||||
|
||||
const char*
|
||||
sg_throwable::getMessage () const
|
||||
sg_throwable::getMessage() const noexcept
|
||||
{
|
||||
return _message;
|
||||
}
|
||||
|
||||
const std::string
|
||||
sg_throwable::getFormattedMessage () const
|
||||
std::string
|
||||
sg_throwable::getFormattedMessage() const noexcept
|
||||
{
|
||||
return std::string(getMessage());
|
||||
std::string ret = getMessage();
|
||||
const std::string loc = getLocation().asString();
|
||||
if (loc.length()) {
|
||||
ret += "\n at ";
|
||||
ret += loc;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sg_throwable::setMessage (const char* message)
|
||||
void sg_throwable::setMessage(const char* message) noexcept
|
||||
{
|
||||
strncpy(_message, message, MAX_TEXT_LEN);
|
||||
_message[MAX_TEXT_LEN - 1] = '\0';
|
||||
@@ -144,13 +145,12 @@ sg_throwable::setMessage (const char* message)
|
||||
}
|
||||
|
||||
const char*
|
||||
sg_throwable::getOrigin () const
|
||||
sg_throwable::getOrigin() const noexcept
|
||||
{
|
||||
return _origin;
|
||||
}
|
||||
|
||||
void
|
||||
sg_throwable::setOrigin (const char* origin)
|
||||
void sg_throwable::setOrigin(const char* origin) noexcept
|
||||
{
|
||||
if (origin) {
|
||||
strncpy(_origin, origin, MAX_TEXT_LEN);
|
||||
@@ -170,27 +170,27 @@ const char* sg_throwable::what() const noexcept
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sg_location sg_throwable::getLocation() const noexcept
|
||||
{
|
||||
return _location;
|
||||
}
|
||||
|
||||
void sg_throwable::setLocation(const sg_location& location) noexcept
|
||||
{
|
||||
_location = location;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of sg_error class.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_error::sg_error ()
|
||||
: sg_throwable ()
|
||||
sg_error::sg_error(const char* message, const char* origin, bool report) noexcept
|
||||
: sg_throwable(message, origin, {}, report)
|
||||
{
|
||||
}
|
||||
|
||||
sg_error::sg_error (const char* message, const char *origin)
|
||||
: sg_throwable(message, origin)
|
||||
{
|
||||
}
|
||||
|
||||
sg_error::sg_error(const std::string& message, const std::string& origin)
|
||||
: sg_throwable(message.c_str(), origin.c_str())
|
||||
{
|
||||
}
|
||||
|
||||
sg_error::~sg_error ()
|
||||
sg_error::sg_error(const std::string& message, const std::string& origin, bool report) noexcept
|
||||
: sg_throwable(message.c_str(), origin.c_str(), {}, report)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -198,121 +198,81 @@ sg_error::~sg_error ()
|
||||
// Implementation of sg_exception class.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_exception::sg_exception ()
|
||||
: sg_throwable ()
|
||||
{
|
||||
}
|
||||
sg_exception::sg_exception(const char* message, const char* origin,
|
||||
const sg_location& loc, bool report) noexcept
|
||||
: sg_throwable(message, origin, loc, report) {}
|
||||
|
||||
sg_exception::sg_exception(const char *message, const char *origin,
|
||||
const sg_location &loc)
|
||||
: sg_throwable(message, origin, loc) {}
|
||||
sg_exception::sg_exception(const std::string& message,
|
||||
const std::string& origin, const sg_location& loc,
|
||||
bool report) noexcept
|
||||
: sg_throwable(message.c_str(), origin.c_str(), loc, report) {}
|
||||
|
||||
sg_exception::sg_exception(const std::string &message,
|
||||
const std::string &origin, const sg_location &loc)
|
||||
: sg_throwable(message.c_str(), origin.c_str(), loc) {}
|
||||
|
||||
sg_exception::~sg_exception ()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of sg_io_exception.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_io_exception::sg_io_exception ()
|
||||
: sg_exception()
|
||||
sg_io_exception::sg_io_exception(const char* message, const char* origin, bool report)
|
||||
: sg_exception(message, origin, {}, report)
|
||||
{
|
||||
}
|
||||
|
||||
sg_io_exception::sg_io_exception (const char* message, const char* origin)
|
||||
: sg_exception(message, origin)
|
||||
sg_io_exception::sg_io_exception(const char* message,
|
||||
const sg_location& location,
|
||||
const char* origin,
|
||||
bool report)
|
||||
: sg_exception(message, origin, location, report) {}
|
||||
|
||||
sg_io_exception::sg_io_exception(const std::string& message,
|
||||
const std::string& origin,
|
||||
bool report)
|
||||
: sg_exception(message, origin, {}, report)
|
||||
{
|
||||
}
|
||||
|
||||
sg_io_exception::sg_io_exception(const char *message,
|
||||
const sg_location &location,
|
||||
const char *origin)
|
||||
: sg_exception(message, origin, location), _location(location) {}
|
||||
|
||||
sg_io_exception::sg_io_exception( const std::string& message,
|
||||
const std::string& origin )
|
||||
: sg_exception(message, origin)
|
||||
{
|
||||
}
|
||||
|
||||
sg_io_exception::sg_io_exception(const std::string &message,
|
||||
const sg_location &location,
|
||||
const std::string &origin)
|
||||
: sg_exception(message, origin, location), _location(location) {}
|
||||
|
||||
sg_io_exception::~sg_io_exception ()
|
||||
{
|
||||
}
|
||||
|
||||
const std::string
|
||||
sg_io_exception::getFormattedMessage () const
|
||||
{
|
||||
std::string ret = getMessage();
|
||||
std::string loc = getLocation().asString();
|
||||
if (loc.length()) {
|
||||
ret += "\n at ";
|
||||
ret += loc;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const sg_location &
|
||||
sg_io_exception::getLocation () const
|
||||
{
|
||||
return _location;
|
||||
}
|
||||
|
||||
void
|
||||
sg_io_exception::setLocation (const sg_location &location)
|
||||
{
|
||||
_location = location;
|
||||
}
|
||||
sg_io_exception::sg_io_exception(const std::string& message,
|
||||
const sg_location& location,
|
||||
const std::string& origin,
|
||||
bool report)
|
||||
: sg_exception(message, origin, location, report) {}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of sg_format_exception.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_format_exception::sg_format_exception ()
|
||||
: sg_exception()
|
||||
sg_format_exception::sg_format_exception() noexcept
|
||||
: sg_exception()
|
||||
{
|
||||
_text[0] = '\0';
|
||||
}
|
||||
|
||||
sg_format_exception::sg_format_exception (const char* message,
|
||||
const char* text,
|
||||
const char* origin)
|
||||
: sg_exception(message, origin)
|
||||
sg_format_exception::sg_format_exception(const char* message,
|
||||
const char* text,
|
||||
const char* origin,
|
||||
bool report)
|
||||
: sg_exception(message, origin, {}, report)
|
||||
{
|
||||
setText(text);
|
||||
}
|
||||
|
||||
sg_format_exception::sg_format_exception( const std::string& message,
|
||||
const std::string& text,
|
||||
const std::string& origin )
|
||||
: sg_exception(message, origin)
|
||||
sg_format_exception::sg_format_exception(const std::string& message,
|
||||
const std::string& text,
|
||||
const std::string& origin,
|
||||
bool report)
|
||||
: sg_exception(message, origin, {}, report)
|
||||
{
|
||||
setText(text.c_str());
|
||||
}
|
||||
|
||||
sg_format_exception::~sg_format_exception ()
|
||||
{
|
||||
}
|
||||
|
||||
const char*
|
||||
sg_format_exception::getText () const
|
||||
sg_format_exception::getText() const noexcept
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
|
||||
void
|
||||
sg_format_exception::setText (const char* text)
|
||||
void sg_format_exception::setText(const char* text) noexcept
|
||||
{
|
||||
if (text) {
|
||||
strncpy(_text, text, MAX_TEXT_LEN);
|
||||
@@ -328,26 +288,19 @@ sg_format_exception::setText (const char* text)
|
||||
// Implementation of sg_range_exception.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
sg_range_exception::sg_range_exception ()
|
||||
: sg_exception()
|
||||
{
|
||||
}
|
||||
|
||||
sg_range_exception::sg_range_exception (const char* message,
|
||||
const char* origin)
|
||||
: sg_exception(message, origin)
|
||||
sg_range_exception::sg_range_exception(const char* message,
|
||||
const char* origin, bool report)
|
||||
: sg_exception(message, origin, {}, report)
|
||||
{
|
||||
}
|
||||
|
||||
sg_range_exception::sg_range_exception(const std::string& message,
|
||||
const std::string& origin)
|
||||
: sg_exception(message, origin)
|
||||
const std::string& origin,
|
||||
bool report)
|
||||
: sg_exception(message, origin, {}, report)
|
||||
{
|
||||
}
|
||||
|
||||
sg_range_exception::~sg_range_exception ()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -28,23 +28,23 @@ class sg_location
|
||||
{
|
||||
public:
|
||||
enum {max_path = 1024};
|
||||
sg_location ();
|
||||
sg_location(const std::string& path, int line = -1, int column = -1);
|
||||
sg_location(const SGPath& path, int line = -1, int column = -1);
|
||||
explicit sg_location(const char* path, int line = -1, int column = -1);
|
||||
sg_location() noexcept;
|
||||
sg_location(const std::string& path, int line = -1, int column = -1) noexcept;
|
||||
sg_location(const SGPath& path, int line = -1, int column = -1) noexcept;
|
||||
explicit sg_location(const char* path, int line = -1, int column = -1) noexcept;
|
||||
|
||||
~sg_location() = default;
|
||||
|
||||
const char *getPath() const;
|
||||
int getLine() const;
|
||||
int getColumn() const;
|
||||
int getByte() const;
|
||||
const char* getPath() const noexcept;
|
||||
int getLine() const noexcept;
|
||||
int getColumn() const noexcept;
|
||||
int getByte() const noexcept;
|
||||
|
||||
std::string asString() const;
|
||||
bool isValid() const;
|
||||
std::string asString() const noexcept;
|
||||
bool isValid() const noexcept;
|
||||
|
||||
private:
|
||||
void setPath(const char *p);
|
||||
private:
|
||||
void setPath(const char* p) noexcept;
|
||||
|
||||
char _path[max_path];
|
||||
int _line;
|
||||
@@ -60,20 +60,26 @@ class sg_throwable : public std::exception
|
||||
{
|
||||
public:
|
||||
enum {MAX_TEXT_LEN = 1024};
|
||||
sg_throwable ();
|
||||
sg_throwable(const char *message, const char *origin = 0,
|
||||
const sg_location &loc = {});
|
||||
sg_throwable() noexcept;
|
||||
sg_throwable(const char* message, const char* origin = 0,
|
||||
const sg_location& loc = {}, bool report = true) noexcept;
|
||||
|
||||
virtual ~sg_throwable ();
|
||||
virtual const char* getMessage () const;
|
||||
virtual const std::string getFormattedMessage () const;
|
||||
virtual void setMessage (const char* message);
|
||||
virtual const char* getOrigin () const;
|
||||
virtual void setOrigin (const char *origin);
|
||||
virtual ~sg_throwable() noexcept = default;
|
||||
|
||||
virtual const char* getMessage() const noexcept;
|
||||
std::string getFormattedMessage() const noexcept;
|
||||
virtual void setMessage(const char* message) noexcept;
|
||||
virtual const char* getOrigin() const noexcept;
|
||||
virtual void setOrigin(const char* origin) noexcept;
|
||||
virtual const char* what() const noexcept;
|
||||
private:
|
||||
|
||||
sg_location getLocation() const noexcept;
|
||||
void setLocation(const sg_location& location) noexcept;
|
||||
|
||||
private:
|
||||
char _message[MAX_TEXT_LEN];
|
||||
char _origin[MAX_TEXT_LEN];
|
||||
sg_location _location;
|
||||
};
|
||||
|
||||
|
||||
@@ -90,10 +96,10 @@ private:
|
||||
class sg_error : public sg_throwable
|
||||
{
|
||||
public:
|
||||
sg_error ();
|
||||
sg_error (const char* message, const char* origin = 0);
|
||||
sg_error(const std::string &message, const std::string &origin = {});
|
||||
virtual ~sg_error ();
|
||||
sg_error() noexcept = default;
|
||||
sg_error(const char* message, const char* origin = 0, bool report = true) noexcept;
|
||||
sg_error(const std::string& message, const std::string& origin = {}, bool report = true) noexcept;
|
||||
virtual ~sg_error() noexcept = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -114,12 +120,12 @@ public:
|
||||
class sg_exception : public sg_throwable
|
||||
{
|
||||
public:
|
||||
sg_exception ();
|
||||
sg_exception(const char *message, const char *origin = 0,
|
||||
const sg_location &loc = {});
|
||||
sg_exception(const std::string &message, const std::string & = {},
|
||||
const sg_location &loc = {});
|
||||
virtual ~sg_exception ();
|
||||
sg_exception() noexcept = default;
|
||||
sg_exception(const char* message, const char* origin = 0,
|
||||
const sg_location& loc = {}, bool report = true) noexcept;
|
||||
sg_exception(const std::string& message, const std::string& = {},
|
||||
const sg_location& loc = {}, bool report = true) noexcept;
|
||||
virtual ~sg_exception() noexcept = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -137,20 +143,15 @@ public:
|
||||
class sg_io_exception : public sg_exception
|
||||
{
|
||||
public:
|
||||
sg_io_exception ();
|
||||
sg_io_exception (const char* message, const char* origin = 0);
|
||||
sg_io_exception (const char* message, const sg_location &location,
|
||||
const char* origin = 0);
|
||||
sg_io_exception (const std::string &message, const std::string &origin = "");
|
||||
sg_io_exception (const std::string &message, const sg_location &location,
|
||||
const std::string &origin = "");
|
||||
sg_io_exception() noexcept = default;
|
||||
sg_io_exception(const char* message, const char* origin = 0, bool report = true);
|
||||
sg_io_exception(const char* message, const sg_location& location,
|
||||
const char* origin = 0, bool report = true);
|
||||
sg_io_exception(const std::string& message, const std::string& origin = {}, bool report = true);
|
||||
sg_io_exception(const std::string& message, const sg_location& location,
|
||||
const std::string& origin = {}, bool report = true);
|
||||
|
||||
virtual ~sg_io_exception ();
|
||||
virtual const std::string getFormattedMessage () const;
|
||||
virtual const sg_location &getLocation () const;
|
||||
virtual void setLocation (const sg_location &location);
|
||||
private:
|
||||
sg_location _location;
|
||||
virtual ~sg_io_exception() noexcept = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -168,14 +169,15 @@ private:
|
||||
class sg_format_exception : public sg_exception
|
||||
{
|
||||
public:
|
||||
sg_format_exception ();
|
||||
sg_format_exception (const char* message, const char* text,
|
||||
const char* origin = 0);
|
||||
sg_format_exception (const std::string& message, const std::string& text,
|
||||
const std::string& origin = "");
|
||||
virtual ~sg_format_exception ();
|
||||
virtual const char* getText () const;
|
||||
virtual void setText (const char* text);
|
||||
sg_format_exception() noexcept;
|
||||
sg_format_exception(const char* message, const char* text,
|
||||
const char* origin = 0, bool report = true);
|
||||
sg_format_exception(const std::string& message, const std::string& text,
|
||||
const std::string& origin = {}, bool report = true);
|
||||
|
||||
const char* getText() const noexcept;
|
||||
void setText(const char* text) noexcept;
|
||||
|
||||
private:
|
||||
char _text[MAX_TEXT_LEN];
|
||||
};
|
||||
@@ -193,12 +195,14 @@ private:
|
||||
class sg_range_exception : public sg_exception
|
||||
{
|
||||
public:
|
||||
sg_range_exception ();
|
||||
sg_range_exception (const char* message,
|
||||
const char* origin = 0);
|
||||
sg_range_exception (const std::string& message,
|
||||
const std::string& origin = "");
|
||||
virtual ~sg_range_exception ();
|
||||
sg_range_exception() noexcept = default;
|
||||
sg_range_exception(const char* message,
|
||||
const char* origin = 0,
|
||||
bool report = true);
|
||||
sg_range_exception(const std::string& message,
|
||||
const std::string& origin = {},
|
||||
bool report = true);
|
||||
virtual ~sg_range_exception() noexcept = default;
|
||||
};
|
||||
|
||||
using ThrowCallback = std::function<void(
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
#include "subsystem_mgr.hxx"
|
||||
#include "commands.hxx"
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include "SGSmplstat.hxx"
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
const int SG_MAX_SUBSYSTEM_EXCEPTIONS = 4;
|
||||
const char SUBSYSTEM_NAME_SEPARATOR = '.';
|
||||
@@ -828,8 +829,18 @@ SGSubsystemGroup::Member::update (double delta_time_sec)
|
||||
if (++exceptionCount > SG_MAX_SUBSYSTEM_EXCEPTIONS) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "(exceptionCount=" << exceptionCount <<
|
||||
", suspending)");
|
||||
simgear::reportError("suspending subsystem after too many errors:" + name);
|
||||
subsystem->suspend();
|
||||
}
|
||||
} catch (std::bad_alloc& ba) {
|
||||
// attempting to track down source of these on Sentry.io
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "caught bad_alloc processing subsystem:" << name);
|
||||
simgear::reportError("caught bad_alloc processing subsystem:" + name);
|
||||
|
||||
if (++exceptionCount > SG_MAX_SUBSYSTEM_EXCEPTIONS) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "(exceptionCount=" << exceptionCount << ", suspending)");
|
||||
subsystem->suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user