Compare commits
30 Commits
version/20
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e29cae309 | ||
|
|
2a73e6c0d5 | ||
|
|
16b5dd5e78 | ||
|
|
1aba20d642 | ||
|
|
27786d709d | ||
|
|
e0e6a29150 | ||
|
|
dfd6076e19 | ||
|
|
e589ef8627 | ||
|
|
e8c1baa396 | ||
|
|
69ba2617d2 | ||
|
|
34c215ad83 | ||
|
|
2a3bb62001 | ||
|
|
fe46ae09ef | ||
|
|
46f67fce7a | ||
|
|
629e68428f | ||
|
|
100439aadd | ||
|
|
f6a348ba94 | ||
|
|
a54b3ffcee | ||
|
|
47842c9ea7 | ||
|
|
834e25521b | ||
|
|
f78597f010 | ||
|
|
c06e433e74 | ||
|
|
9971d517fd | ||
|
|
f8548029a2 | ||
|
|
3e337e3e97 | ||
|
|
e59f8eda74 | ||
|
|
31e70b205c | ||
|
|
5d02f1db5f | ||
|
|
b01bd93a20 | ||
|
|
05bbba5074 |
@@ -249,8 +249,8 @@ else()
|
||||
# declaring symbols as declspec(import)
|
||||
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
|
||||
set(EXPAT_INCLUDE_DIRS
|
||||
${PROJECT_SOURCE_DIR}/3rdparty/expat
|
||||
${PROJECT_BINARY_DIR}/3rdparty/expat)
|
||||
${PROJECT_SOURCE_DIR}/3rdparty/expat
|
||||
${PROJECT_BINARY_DIR}/3rdparty/expat)
|
||||
endif(SYSTEM_EXPAT)
|
||||
|
||||
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
||||
@@ -264,9 +264,17 @@ if(HAVE_INTTYPES_H)
|
||||
endif()
|
||||
|
||||
if(ENABLE_RTI)
|
||||
# See if we have any rti library variant installed
|
||||
message(STATUS "RTI: ENABLED")
|
||||
find_package(RTI)
|
||||
find_package(PkgConfig)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
SET(ENV{PKG_CONFIG_PATH} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
||||
pkg_check_modules(RTI hla-rti13)
|
||||
endif(PKG_CONFIG_FOUND)
|
||||
if(RTI_FOUND)
|
||||
SET(RTI_INCLUDE_DIR "${RTI_INCLUDE_DIRS}")
|
||||
message(STATUS "RTI: ENABLED")
|
||||
else()
|
||||
message(STATUS "RTI: DISABLED")
|
||||
endif(RTI_FOUND)
|
||||
else()
|
||||
message(STATUS "RTI: DISABLED")
|
||||
endif(ENABLE_RTI)
|
||||
@@ -274,7 +282,7 @@ endif(ENABLE_RTI)
|
||||
if(ENABLE_GDAL)
|
||||
find_package(GDAL 2.0.0 REQUIRED)
|
||||
if (GDAL_FOUND)
|
||||
include_directories(${GDAL_INCLUDE_DIR})
|
||||
include_directories(${GDAL_INCLUDE_DIR})
|
||||
endif(GDAL_FOUND)
|
||||
endif(ENABLE_GDAL)
|
||||
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
%define ver @VERSION@
|
||||
%define rel 1
|
||||
%define prefix /usr
|
||||
|
||||
Summary: Simulator Construction Gear.
|
||||
Name: @PACKAGE@
|
||||
Version: %ver
|
||||
Release: %rel
|
||||
Copyright: LGPL
|
||||
Group: Libraries/Graphics
|
||||
Source: %{name}-%{version}.tar.gz
|
||||
#URL:
|
||||
BuildRoot: /tmp/%{name}-%{version}-%{rel}-root
|
||||
Packager: Fill In As You Wish
|
||||
Docdir: %{prefix}/doc
|
||||
|
||||
%description
|
||||
This package contains a tools and libraries useful for constructing
|
||||
simulation and visualization applications such as FlightGear or TerraGear.
|
||||
|
||||
Authors:
|
||||
N/A
|
||||
|
||||
%prep
|
||||
%setup -n %{name}-%{version}
|
||||
|
||||
|
||||
%build
|
||||
# Needed for snapshot releases.
|
||||
if [ ! -f configure ]; then
|
||||
CFLAGS="$RPM_OPT_FLAGS" ./autogen.sh --prefix=%prefix
|
||||
else
|
||||
CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%prefix
|
||||
fi
|
||||
|
||||
if [ "$SMP" != "" ]; then
|
||||
JSMP = '"MAKE=make -k -j $SMP"'
|
||||
fi
|
||||
|
||||
make ${JSMP};
|
||||
|
||||
|
||||
%install
|
||||
[ -d ${RPM_BUILD_ROOT} ] && rm -rf ${RPM_BUILD_ROOT}
|
||||
|
||||
make prefix=${RPM_BUILD_ROOT}%{prefix} install
|
||||
|
||||
#
|
||||
# Generating file lists and store them in file-lists
|
||||
# Starting with the directory listings
|
||||
#
|
||||
find ${RPM_BUILD_ROOT}%{prefix}/{bin,include,lib} -type d | sed "s#^${RPM_BUILD_ROOT}#\%attr (-\,root\,root) \%dir #" > file-lists
|
||||
%{?ETCDR:find ${RPM_BUILD_ROOT}%{!?SYSCF:%{prefix}}/etc -type d | sed "s#^${RPM_BUILD_ROOT}#\%attr (-\,root\,root) \%dir #" >> file-lists}
|
||||
|
||||
#
|
||||
# Then, the file listings
|
||||
#
|
||||
echo "%defattr (-, root, root)" >> file-lists
|
||||
%{?ETCDR:find ${RPM_BUILD_ROOT}%{!?SYSCF:%{prefix}}/etc/%{name}.conf -type f | sed -e "s#^${RPM_BUILD_ROOT}#%config #g" >> file-lists}
|
||||
find ${RPM_BUILD_ROOT}%{prefix} -type f | sed -e "s#^${RPM_BUILD_ROOT}##g" >> file-lists
|
||||
|
||||
|
||||
%clean
|
||||
(cd ..; rm -rf %{name}-%{version} ${RPM_BUILD_ROOT})
|
||||
|
||||
|
||||
%files -f file-lists
|
||||
%defattr (-, root, root)
|
||||
%doc AUTHORS
|
||||
%doc COPYING
|
||||
%doc ChangeLog
|
||||
%doc INSTALL
|
||||
%doc NEWS
|
||||
%doc README
|
||||
%doc %{name}.spec.in
|
||||
|
||||
@@ -5,6 +5,7 @@ foreach( mylibfolder
|
||||
bucket
|
||||
bvh
|
||||
debug
|
||||
embedded_resources
|
||||
ephemeris
|
||||
io
|
||||
magvar
|
||||
@@ -172,6 +173,11 @@ if(NOT SIMGEAR_HEADLESS)
|
||||
${OPENGL_LIBRARY}
|
||||
${JPEG_LIBRARY})
|
||||
|
||||
if(ENABLE_GDAL)
|
||||
target_link_libraries(SimGearScene
|
||||
${GDAL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# only actually needed by canvas/KeyboardEvent.cxx
|
||||
target_include_directories(SimGearScene PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/utf8/source)
|
||||
endif()
|
||||
|
||||
@@ -531,7 +531,7 @@ logstream::log( sgDebugClass c, sgDebugPriority p,
|
||||
}
|
||||
|
||||
|
||||
void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, int columns)
|
||||
void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns)
|
||||
{
|
||||
unsigned int i, j;
|
||||
char temp[3000], temp1[3000];
|
||||
|
||||
@@ -120,7 +120,7 @@ public:
|
||||
/**
|
||||
* output formatted hex dump of memory block
|
||||
*/
|
||||
void hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, int columns = 16);
|
||||
void hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns = 16);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
14
simgear/embedded_resources/CMakeLists.txt
Normal file
14
simgear/embedded_resources/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
include (SimGearComponent)
|
||||
|
||||
set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx)
|
||||
set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx)
|
||||
|
||||
simgear_component(embedded_resources embedded_resources
|
||||
"${SOURCES}" "${HEADERS}")
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
add_executable(test_embedded_resources embedded_resources_test.cxx)
|
||||
target_link_libraries(test_embedded_resources ${TEST_LIBS})
|
||||
add_test(embedded_resources
|
||||
${EXECUTABLE_OUTPUT_PATH}/test_embedded_resources)
|
||||
endif(ENABLE_TESTS)
|
||||
265
simgear/embedded_resources/EmbeddedResource.cxx
Normal file
265
simgear/embedded_resources/EmbeddedResource.cxx
Normal file
@@ -0,0 +1,265 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResource.cxx --- Class for pointing to/accessing an embedded resource
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
#include <ios> // std::streamsize
|
||||
#include <ostream>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <utility> // std::move()
|
||||
#include <algorithm> // std::min()
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/io/iostreams/CharArrayStream.hxx>
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
|
||||
// Inspired by <http://stackoverflow.com/a/21174979/4756009>
|
||||
template<typename Derived, typename Base>
|
||||
static unique_ptr<Derived> static_unique_ptr_cast(unique_ptr<Base> p)
|
||||
{
|
||||
auto d = static_cast<Derived *>(p.release());
|
||||
return unique_ptr<Derived>(d);
|
||||
}
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * AbstractEmbeddedResource class *
|
||||
// ***************************************************************************
|
||||
|
||||
AbstractEmbeddedResource::AbstractEmbeddedResource(const char *data,
|
||||
std::size_t size)
|
||||
: _data(data),
|
||||
_size(size)
|
||||
{ }
|
||||
|
||||
const char *AbstractEmbeddedResource::rawPtr() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
std::size_t AbstractEmbeddedResource::rawSize() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
string AbstractEmbeddedResource::str() const
|
||||
{
|
||||
if (_size > std::numeric_limits<string::size_type>::max()) {
|
||||
throw sg_range_exception(
|
||||
"Resource too large to fit in an std::string (size: " +
|
||||
std::to_string(_size) + " bytes)");
|
||||
}
|
||||
|
||||
return string(_data, _size);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * RawEmbeddedResource class *
|
||||
// ***************************************************************************
|
||||
|
||||
RawEmbeddedResource::RawEmbeddedResource(const char *data, std::size_t size)
|
||||
: AbstractEmbeddedResource(data, size)
|
||||
{ }
|
||||
|
||||
AbstractEmbeddedResource::CompressionType
|
||||
RawEmbeddedResource::compressionType() const
|
||||
{
|
||||
return AbstractEmbeddedResource::CompressionType::NONE;
|
||||
}
|
||||
|
||||
string RawEmbeddedResource::compressionDescr() const
|
||||
{
|
||||
return string("none");
|
||||
}
|
||||
|
||||
unique_ptr<std::streambuf> RawEmbeddedResource::streambuf() const
|
||||
{
|
||||
// This is a read-only variant of CharArrayStreambuf
|
||||
return unique_ptr<std::streambuf>(
|
||||
new ROCharArrayStreambuf(rawPtr(), rawSize()));
|
||||
}
|
||||
|
||||
unique_ptr<std::istream> RawEmbeddedResource::istream() const
|
||||
{
|
||||
return unique_ptr<std::istream>(new CharArrayIStream(rawPtr(), rawSize()));
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * ZlibEmbeddedResource class *
|
||||
// ***************************************************************************
|
||||
|
||||
ZlibEmbeddedResource::ZlibEmbeddedResource(const char *data,
|
||||
std::size_t compressedSize,
|
||||
std::size_t uncompressedSize)
|
||||
: AbstractEmbeddedResource(data, compressedSize),
|
||||
_uncompressedSize(uncompressedSize),
|
||||
_inBuf(nullptr),
|
||||
_inBufSize(262144), // adjusted below in the constructor body
|
||||
_outBuf(nullptr),
|
||||
_outBufSize(262144),
|
||||
_putbackSize(0) // default for best performance
|
||||
{
|
||||
static_assert(262144 <= std::numeric_limits<std::size_t>::max(),
|
||||
"The std::size_t type is unexpectedly small.");
|
||||
// No need to use an input buffer (where compressed data chunks are put for
|
||||
// zlib to read and decompress) larger than the whole compressed resource!
|
||||
_inBufSize = std::min(rawSize(), _inBufSize);
|
||||
}
|
||||
|
||||
AbstractEmbeddedResource::CompressionType
|
||||
ZlibEmbeddedResource::compressionType() const
|
||||
{ return AbstractEmbeddedResource::CompressionType::ZLIB; }
|
||||
|
||||
string ZlibEmbeddedResource::compressionDescr() const
|
||||
{ return string("zlib"); }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::uncompressedSize() const
|
||||
{ return _uncompressedSize; }
|
||||
|
||||
char* ZlibEmbeddedResource::getInputBufferStart()
|
||||
{ return _inBuf; }
|
||||
|
||||
void ZlibEmbeddedResource::setInputBufferStart(char* inBuf)
|
||||
{ _inBuf = inBuf; }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::getInputBufferSize()
|
||||
{ return _inBufSize; }
|
||||
|
||||
void ZlibEmbeddedResource::setInputBufferSize(std::size_t size)
|
||||
{ _inBufSize = size; }
|
||||
|
||||
char* ZlibEmbeddedResource::getOutputBufferStart()
|
||||
{ return _outBuf; }
|
||||
|
||||
void ZlibEmbeddedResource::setOutputBufferStart(char* outBuf)
|
||||
{ _outBuf = outBuf; }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::getOutputBufferSize()
|
||||
{ return _outBufSize; }
|
||||
|
||||
void ZlibEmbeddedResource::setOutputBufferSize(std::size_t size)
|
||||
{ _outBufSize = size; }
|
||||
|
||||
std::size_t ZlibEmbeddedResource::getPutbackSize()
|
||||
{ return _putbackSize; }
|
||||
|
||||
void ZlibEmbeddedResource::setPutbackSize(std::size_t size)
|
||||
{ _putbackSize = size; }
|
||||
|
||||
unique_ptr<std::streambuf> ZlibEmbeddedResource::streambuf() const
|
||||
{
|
||||
unique_ptr<CharArrayIStream> rawReaderIStream(
|
||||
new CharArrayIStream(rawPtr(), rawSize()));
|
||||
|
||||
return unique_ptr<std::streambuf>(
|
||||
new ZlibDecompressorIStreambuf(
|
||||
std::move(rawReaderIStream),
|
||||
SGPath(), // rawReaderIStream isn't bound to a file
|
||||
ZLibCompressionFormat::ZLIB,
|
||||
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
|
||||
}
|
||||
|
||||
unique_ptr<std::istream> ZlibEmbeddedResource::istream() const
|
||||
{
|
||||
unique_ptr<CharArrayIStream> rawReaderIStream(
|
||||
new CharArrayIStream(rawPtr(), rawSize()));
|
||||
|
||||
return unique_ptr<std::istream>(
|
||||
new ZlibDecompressorIStream(
|
||||
std::move(rawReaderIStream),
|
||||
SGPath(), // rawReaderIStream isn't bound to a file
|
||||
ZLibCompressionFormat::ZLIB,
|
||||
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
|
||||
}
|
||||
|
||||
std::string ZlibEmbeddedResource::str() const
|
||||
{
|
||||
static constexpr std::size_t bufSize = 65536;
|
||||
static_assert(bufSize <= std::numeric_limits<std::streamsize>::max(),
|
||||
"Type std::streamsize is unexpectedly small");
|
||||
static_assert(bufSize <= std::numeric_limits<string::size_type>::max(),
|
||||
"Type std::string::size_type is unexpectedly small");
|
||||
unique_ptr<char[]> buf(new char[bufSize]);
|
||||
|
||||
auto decompressor =
|
||||
static_unique_ptr_cast<ZlibDecompressorIStream>(istream());
|
||||
std::streamsize nbCharsRead;
|
||||
string result;
|
||||
|
||||
if (_uncompressedSize > std::numeric_limits<string::size_type>::max()) {
|
||||
throw sg_range_exception(
|
||||
"Resource too large to fit in an std::string (uncompressed size: "
|
||||
+ std::to_string(_uncompressedSize) + " bytes)");
|
||||
} else {
|
||||
result.reserve(static_cast<string::size_type>(_uncompressedSize));
|
||||
}
|
||||
|
||||
do {
|
||||
decompressor->read(buf.get(), bufSize);
|
||||
nbCharsRead = decompressor->gcount();
|
||||
|
||||
if (nbCharsRead > 0) {
|
||||
result.append(buf.get(), nbCharsRead);
|
||||
}
|
||||
} while (*decompressor);
|
||||
|
||||
// decompressor->fail() would *not* indicate an error, due to the semantics
|
||||
// of std::istream::read().
|
||||
if (decompressor->bad()) {
|
||||
throw sg_io_exception("Error while extracting a compressed resource");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * Stream insertion operators *
|
||||
// ***************************************************************************
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const RawEmbeddedResource& resource)
|
||||
{ // This won't escape double quotes, backslashes, etc. in resource.str().
|
||||
return os << "RawEmbeddedResource:\n"
|
||||
" compressionType = \"" << resource.compressionDescr() << "\"\n"
|
||||
" rawPtr = " << (void*) resource.rawPtr() << "\n"
|
||||
" rawSize = " << resource.rawSize();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const ZlibEmbeddedResource& resource)
|
||||
{ // This won't escape double quotes, backslashes, etc. in resource.str().
|
||||
return os << "ZlibEmbeddedResource:\n"
|
||||
" compressionType = \"" << resource.compressionDescr() << "\"\n"
|
||||
" rawPtr = " << (void*) resource.rawPtr() << "\n"
|
||||
" rawSize = " << resource.rawSize() << "\n"
|
||||
" uncompressedSize = " << resource.uncompressedSize();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
163
simgear/embedded_resources/EmbeddedResource.hxx
Normal file
163
simgear/embedded_resources/EmbeddedResource.hxx
Normal file
@@ -0,0 +1,163 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResource.hxx --- Class for pointing to/accessing an embedded resource
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_EMBEDDEDRESOURCE_HXX
|
||||
#define FG_EMBEDDEDRESOURCE_HXX
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// Abstract base class for embedded resources
|
||||
class AbstractEmbeddedResource
|
||||
{
|
||||
public:
|
||||
enum class CompressionType {
|
||||
NONE = 0,
|
||||
ZLIB
|
||||
};
|
||||
|
||||
// Constructor.
|
||||
//
|
||||
// 'data' and 'size' indicate the resource contents. There is no requirement
|
||||
// of null-termination, including for text data (given how
|
||||
// EmbeddedResourceManager::getString() works, including a null terminator
|
||||
// for text contents is actually counter-productive). The data may be of
|
||||
// arbitrary type and size: binary, text, whatever. The constructed object
|
||||
// (for derived classes since this one is abstract) does *not* hold a copy
|
||||
// of the data, it just keeps a pointer to it and provides methods to access
|
||||
// it. The data must therefore remain available as long as the object is in
|
||||
// use---this class was designed for use with data stored in static
|
||||
// variables.
|
||||
explicit AbstractEmbeddedResource(const char *data, std::size_t size);
|
||||
AbstractEmbeddedResource(const AbstractEmbeddedResource&) = default;
|
||||
AbstractEmbeddedResource(AbstractEmbeddedResource&&) = default;
|
||||
AbstractEmbeddedResource& operator=(const AbstractEmbeddedResource&) = default;
|
||||
AbstractEmbeddedResource& operator=(AbstractEmbeddedResource&&) = default;
|
||||
virtual ~AbstractEmbeddedResource() = default;
|
||||
|
||||
// Return the pointer to beginning-of-resource contents---the same that was
|
||||
// passed to the constructor.
|
||||
const char *rawPtr() const;
|
||||
// Return the resource size, as passed to the constructor. For a compressed
|
||||
// resource, this is the compressed size; such resources provide an
|
||||
// additional uncompressedSize() method.
|
||||
std::size_t rawSize() const;
|
||||
|
||||
// Return an std::string object containing a copy of the resource contents.
|
||||
// For a compressed resource, this is the data obtained after decompression.
|
||||
virtual std::string str() const;
|
||||
// Return an std::streambuf instance providing read-only access to the
|
||||
// resource contents (in uncompressed form for compressed resources). This
|
||||
// allows memory-friendly access to large resources by enabling incremental
|
||||
// processing with transparent decompression for compressed resources.
|
||||
virtual std::unique_ptr<std::streambuf> streambuf() const = 0;
|
||||
// Return an std::istream instance providing read-only access to the
|
||||
// resource contents (in uncompressed form for compressed resources).
|
||||
//
|
||||
// The same remark as for streambuf() applies. std::istream is simply a
|
||||
// higher-level interface than std::streambuf, otherwise both allow the same
|
||||
// kind of processing.
|
||||
virtual std::unique_ptr<std::istream> istream() const = 0;
|
||||
|
||||
// Return the resource compression type.
|
||||
virtual CompressionType compressionType() const = 0;
|
||||
// Return a string description of the resource compression type. Examples:
|
||||
// "none", "zlib".
|
||||
virtual std::string compressionDescr() const = 0;
|
||||
|
||||
private:
|
||||
// Pointer to the start of resource contents
|
||||
const char *_data;
|
||||
// Size of resource contents, in bytes
|
||||
std::size_t _size;
|
||||
};
|
||||
|
||||
// Class to describe an uncompressed resource. See AbstractEmbeddedResource.
|
||||
class RawEmbeddedResource : public AbstractEmbeddedResource
|
||||
{
|
||||
public:
|
||||
explicit RawEmbeddedResource(const char *data, std::size_t size);
|
||||
|
||||
AbstractEmbeddedResource::CompressionType compressionType() const override;
|
||||
std::string compressionDescr() const override;
|
||||
|
||||
// The str() method is inherited from AbstractEmbeddedResource
|
||||
std::unique_ptr<std::streambuf> streambuf() const override;
|
||||
std::unique_ptr<std::istream> istream() const override;
|
||||
};
|
||||
|
||||
// Class to describe a zlib-compressed resource.
|
||||
//
|
||||
// Instances of this class point to resource contents stored in the stream
|
||||
// format documented in RFC 1950.
|
||||
class ZlibEmbeddedResource : public AbstractEmbeddedResource
|
||||
{
|
||||
public:
|
||||
explicit ZlibEmbeddedResource(const char *data, std::size_t compressedSize,
|
||||
std::size_t uncompressedSize);
|
||||
|
||||
AbstractEmbeddedResource::CompressionType compressionType() const override;
|
||||
std::string compressionDescr() const override;
|
||||
// Return the resource uncompressed size, in bytes.
|
||||
std::size_t uncompressedSize() const;
|
||||
|
||||
std::string str() const override;
|
||||
std::unique_ptr<std::streambuf> streambuf() const override;
|
||||
std::unique_ptr<std::istream> istream() const override;
|
||||
|
||||
// Getters and setters for parameters used in streambuf() and istream().
|
||||
// Calling any of the setters affects the subsequent streambuf() and
|
||||
// istream() calls.
|
||||
char* getInputBufferStart();
|
||||
void setInputBufferStart(char* inBuf);
|
||||
std::size_t getInputBufferSize();
|
||||
void setInputBufferSize(std::size_t size);
|
||||
char* getOutputBufferStart();
|
||||
void setOutputBufferStart(char* outBuf);
|
||||
std::size_t getOutputBufferSize();
|
||||
void setOutputBufferSize(std::size_t size);
|
||||
std::size_t getPutbackSize();
|
||||
void setPutbackSize(std::size_t size);
|
||||
|
||||
private:
|
||||
std::size_t _uncompressedSize;
|
||||
char* _inBuf;
|
||||
std::size_t _inBufSize;
|
||||
char* _outBuf;
|
||||
std::size_t _outBufSize;
|
||||
std::size_t _putbackSize;
|
||||
};
|
||||
|
||||
// These functions are essentially intended for troubleshooting purposes.
|
||||
std::ostream& operator<<(std::ostream&, const RawEmbeddedResource&);
|
||||
std::ostream& operator<<(std::ostream&, const ZlibEmbeddedResource&);
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of FG_EMBEDDEDRESOURCE_HXX
|
||||
235
simgear/embedded_resources/EmbeddedResourceManager.cxx
Normal file
235
simgear/embedded_resources/EmbeddedResourceManager.cxx
Normal file
@@ -0,0 +1,235 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResourceManager.cxx --- Manager class for resources embedded in an
|
||||
// executable
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility> // std::move()
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
#include "EmbeddedResourceManager.hxx"
|
||||
#include "EmbeddedResourceManager_private.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::shared_ptr;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
static unique_ptr<EmbeddedResourceManager> staticInstance;
|
||||
|
||||
// ***************************************************************************
|
||||
// * EmbeddedResourceManager::Impl *
|
||||
// ***************************************************************************
|
||||
EmbeddedResourceManager::Impl::Impl()
|
||||
: dirty(true)
|
||||
{ }
|
||||
|
||||
void
|
||||
EmbeddedResourceManager::Impl::rehash()
|
||||
{
|
||||
// Update the list of resource pools to search when looking up a resource.
|
||||
// This allows to optimize resource lookup: no need to parse, split and hash
|
||||
// the same locale string every time to find the corresponding resource
|
||||
// pools.
|
||||
poolSearchList = listOfResourcePoolsToSearch(selectedLocale);
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::Impl::getLocale() const
|
||||
{
|
||||
return selectedLocale;
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::Impl::selectLocale(const std::string& locale)
|
||||
{
|
||||
string previousLocale = std::move(selectedLocale);
|
||||
selectedLocale = locale;
|
||||
dirty = true;
|
||||
|
||||
return previousLocale;
|
||||
}
|
||||
|
||||
// Static method
|
||||
std::vector<string>
|
||||
EmbeddedResourceManager::Impl::localesSearchList(const string& locale)
|
||||
{
|
||||
std::vector<string> result;
|
||||
|
||||
if (locale.empty()) {
|
||||
result.push_back(string()); // only the default locale
|
||||
} else {
|
||||
std::size_t sepIdx = locale.find_first_of('_');
|
||||
|
||||
if (sepIdx == string::npos) {
|
||||
// Try the given “locale” first (e.g., fr), then the default locale
|
||||
result = std::vector<string>({locale, string()});
|
||||
} else {
|
||||
string langCode = locale.substr(0, sepIdx);
|
||||
// Try the given “locale” first (e.g., fr_FR), then the language code
|
||||
// (e.g., fr) and finally the default locale
|
||||
result = std::vector<string>({locale, langCode, string()});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto
|
||||
EmbeddedResourceManager::Impl::listOfResourcePoolsToSearch(
|
||||
const string& locale) const
|
||||
-> std::vector< shared_ptr<ResourcePool> >
|
||||
{
|
||||
std::vector<string> searchedLocales = localesSearchList(locale);
|
||||
std::vector< shared_ptr<ResourcePool> > result;
|
||||
|
||||
for (const string& loc: searchedLocales) {
|
||||
auto poolPtrIt = localeToResourcePoolMap.find(loc);
|
||||
// Don't store pointers to empty resource pools in 'result'. This
|
||||
// optimizes resource fetching a little bit, but requires that all
|
||||
// resources are added before this method is called.
|
||||
if (poolPtrIt != localeToResourcePoolMap.end()) {
|
||||
// Copy a shared_ptr<ResourcePool>
|
||||
result.push_back(poolPtrIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Static method
|
||||
shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::Impl::lookupResourceInPools(
|
||||
const string& virtualPath,
|
||||
const std::vector< shared_ptr<ResourcePool> >& aPoolSearchList)
|
||||
{
|
||||
// Search the provided resource pools in proper order. For instance, the one
|
||||
// for 'fr_FR', then the one for 'fr' and finally the one for the default
|
||||
// locale. Return the first resource found in one of these pools.
|
||||
for (const shared_ptr<ResourcePool>& poolPtr: aPoolSearchList) {
|
||||
auto resourcePtrIt = poolPtr->find(virtualPath);
|
||||
|
||||
if (resourcePtrIt != poolPtr->end()) {
|
||||
// Copy a shared_ptr<const AbstractEmbeddedResource>
|
||||
return resourcePtrIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
return shared_ptr<const AbstractEmbeddedResource>(); // null shared_ptr object
|
||||
}
|
||||
|
||||
void
|
||||
EmbeddedResourceManager::Impl::addResource(
|
||||
const string& virtualPath,
|
||||
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const string& locale)
|
||||
{
|
||||
// Find the resource pool corresponding to the specified locale
|
||||
shared_ptr<ResourcePool>& resPoolPtr = localeToResourcePoolMap[locale];
|
||||
if (!resPoolPtr) {
|
||||
resPoolPtr.reset(new ResourcePool());
|
||||
}
|
||||
|
||||
auto emplaceRetval = resPoolPtr->emplace(virtualPath, std::move(resourcePtr));
|
||||
|
||||
if (!emplaceRetval.second) {
|
||||
const string localeDescr =
|
||||
(locale.empty()) ? "the default locale" : "locale '" + locale + "'";
|
||||
throw sg_error(
|
||||
"Virtual path already in use for " + localeDescr +
|
||||
" in the EmbeddedResourceManager: '" + virtualPath + "'");
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * EmbeddedResourceManager *
|
||||
// ***************************************************************************
|
||||
EmbeddedResourceManager::EmbeddedResourceManager()
|
||||
: p(unique_ptr<Impl>(new Impl))
|
||||
{ }
|
||||
|
||||
const unique_ptr<EmbeddedResourceManager>&
|
||||
EmbeddedResourceManager::createInstance()
|
||||
{
|
||||
staticInstance.reset(new EmbeddedResourceManager);
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
const unique_ptr<EmbeddedResourceManager>&
|
||||
EmbeddedResourceManager::instance()
|
||||
{
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::getLocale() const
|
||||
{
|
||||
return p->getLocale();
|
||||
}
|
||||
|
||||
string
|
||||
EmbeddedResourceManager::selectLocale(const std::string& locale)
|
||||
{
|
||||
return p->selectLocale(locale);
|
||||
}
|
||||
|
||||
void
|
||||
EmbeddedResourceManager::addResource(
|
||||
const string& virtualPath,
|
||||
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const string& locale)
|
||||
{
|
||||
p->addResource(virtualPath, std::move(resourcePtr), locale);
|
||||
}
|
||||
|
||||
shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath) const
|
||||
{
|
||||
if (p->dirty) {
|
||||
p->rehash(); // update p->poolSearchList
|
||||
}
|
||||
|
||||
// Use the selected locale
|
||||
return p->lookupResourceInPools(virtualPath, p->poolSearchList);
|
||||
}
|
||||
|
||||
shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath,
|
||||
const string& locale) const
|
||||
{
|
||||
// In this overload, we don't use the cached list of pools
|
||||
// (p->poolSearchList), therefore there is no need to check the 'dirty' flag
|
||||
// or to rehash().
|
||||
return p->lookupResourceInPools(virtualPath,
|
||||
p->listOfResourcePoolsToSearch(locale));
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
204
simgear/embedded_resources/EmbeddedResourceManager.hxx
Normal file
204
simgear/embedded_resources/EmbeddedResourceManager.hxx
Normal file
@@ -0,0 +1,204 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResourceManager.hxx --- Manager class for resources embedded in an
|
||||
// executable
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_EMBEDDEDRESOURCEMANAGER_HXX
|
||||
#define FG_EMBEDDEDRESOURCEMANAGER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <memory> // std::unique_ptr, std::shared_ptr
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <utility> // std::forward()
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class EmbeddedResourceManager
|
||||
{
|
||||
public:
|
||||
EmbeddedResourceManager(const EmbeddedResourceManager&) = delete;
|
||||
EmbeddedResourceManager& operator=(const EmbeddedResourceManager&) = delete;
|
||||
EmbeddedResourceManager(EmbeddedResourceManager&&) = delete;
|
||||
EmbeddedResourceManager& operator=(EmbeddedResourceManager&&) = delete;
|
||||
// The instance is created by createInstance() -> private constructor
|
||||
// but it should be deleted by its owning std::unique_ptr -> public destructor
|
||||
~EmbeddedResourceManager() = default;
|
||||
|
||||
// Static creator
|
||||
static const std::unique_ptr<EmbeddedResourceManager>& createInstance();
|
||||
|
||||
// Singleton accessor
|
||||
static const std::unique_ptr<EmbeddedResourceManager>& instance();
|
||||
|
||||
// Return the currently-selected “locale”[*] for resource fetching.
|
||||
//
|
||||
// [*] For instance: std::string("") for the default locale
|
||||
// std::string("fr") for French
|
||||
// std::string("fr_FR") for French from France
|
||||
std::string getLocale() const;
|
||||
// Select the locale for which resources will be returned in the future, for
|
||||
// the getResourceOrNullPtr(), getResource(), getString(), getStreambuf()
|
||||
// and getIStream() overloads that don't have a 'locale' parameter.
|
||||
// May be called several times. Return the previously-selected locale.
|
||||
//
|
||||
// If you just want to fetch one or two resources in a particular “locale”
|
||||
// (language), it is simpler to use an overload of one of the
|
||||
// getResourceOrNullPtr(), getResource(), ..., getIStream() methods that has
|
||||
// a 'locale' parameter.
|
||||
std::string selectLocale(const std::string& locale);
|
||||
|
||||
// Add a resource for the specified locale to the embedded resource manager.
|
||||
// This method acts as a sink for its second argument (the std::unique_ptr
|
||||
// typically has to be std::move()d). If 'locale' is empty, the resource is
|
||||
// added for the default locale.
|
||||
void addResource(const std::string& virtualPath,
|
||||
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const std::string& locale = std::string());
|
||||
|
||||
// Get access to a resource.
|
||||
//
|
||||
// Fetch the resource for the selected locale (cf. selectLocale()), with
|
||||
// fallback behavior[1]. If no resource is found for the given
|
||||
// 'virtualPath', return a null
|
||||
// std::shared_ptr<const AbstractEmbeddedResource>.
|
||||
//
|
||||
// [1] This means that for instance, if the selected locale is 'es_ES', the
|
||||
// resource is first looked up for the 'es_ES' “locale”; then, if not
|
||||
// found, for 'es'; and finally, if still not found, for the default
|
||||
// locale ''.
|
||||
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
|
||||
const std::string& virtualPath) const;
|
||||
// Same as the previous overload, except the resource is fetched for the
|
||||
// specified locale (with fallback behavior) instead of for the selected
|
||||
// locale. Use an empty 'locale' parameter to fetch the resource for the
|
||||
// default locale.
|
||||
std::shared_ptr<const AbstractEmbeddedResource> getResourceOrNullPtr(
|
||||
const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
|
||||
// Same overloads as for getResourceOrNullPtr(), except that if the resource
|
||||
// isn't found, then an sg_exception is raised. These methods never return
|
||||
// a null or empty std::shared_ptr<const AbstractEmbeddedResource>.
|
||||
template <typename ...Args>
|
||||
std::shared_ptr<const AbstractEmbeddedResource> getResource(
|
||||
const std::string& virtualPath, Args&& ...args) const
|
||||
{
|
||||
const auto resPtr = getResourceOrNullPtr(virtualPath,
|
||||
std::forward<Args>(args)...);
|
||||
|
||||
if (!resPtr) {
|
||||
throw sg_exception("No embedded resource found at virtual path '" +
|
||||
virtualPath + "'");
|
||||
}
|
||||
|
||||
return resPtr;
|
||||
}
|
||||
|
||||
// Get a resource contents in the form of an std::string. Raise an
|
||||
// sg_exception if no resource is found for the specified 'virtualPath'.
|
||||
//
|
||||
// The returned std::string is a copy of the resource contents (possibly
|
||||
// transparently decompressed, cf. simgear::ZlibEmbeddedResource).
|
||||
template <typename ...Args>
|
||||
std::string getString(const std::string& virtualPath, Args&& ...args) const
|
||||
{
|
||||
return getResource(virtualPath, std::forward<Args>(args)...)->str();
|
||||
}
|
||||
|
||||
// Get access to a resource via an std::streambuf instance. Raise an
|
||||
// sg_exception if no resource is found for the specified 'virtualPath'.
|
||||
//
|
||||
// This allows one to incrementally process the resource contents without
|
||||
// ever making a copy of it (including incremental, transparent
|
||||
// decompression if the resource happens to be compressed---cf.
|
||||
// simgear::ZlibEmbeddedResource).
|
||||
template <typename ...Args>
|
||||
std::unique_ptr<std::streambuf> getStreambuf(const std::string& virtualPath,
|
||||
Args&& ...args) const
|
||||
{
|
||||
return getResource(virtualPath, std::forward<Args>(args)...)->streambuf();
|
||||
}
|
||||
|
||||
// Get access to a resource via an std::istream instance. Raise an
|
||||
// sg_exception if no resource is found for the specified 'virtualPath'.
|
||||
//
|
||||
// The same remarks made for getStreambuf() apply here too.
|
||||
template <typename ...Args>
|
||||
std::unique_ptr<std::istream> getIStream(const std::string& virtualPath,
|
||||
Args&& ...args) const
|
||||
{
|
||||
return getResource(virtualPath, std::forward<Args>(args)...)->istream();
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor called from createInstance() only
|
||||
explicit EmbeddedResourceManager();
|
||||
|
||||
class Impl;
|
||||
const std::unique_ptr<Impl> p; // Pimpl idiom
|
||||
};
|
||||
|
||||
// Explicit template instantiations
|
||||
template
|
||||
std::shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResource(const std::string& virtualPath) const;
|
||||
|
||||
template
|
||||
std::string
|
||||
EmbeddedResourceManager::getString(const std::string& virtualPath) const;
|
||||
|
||||
template
|
||||
std::unique_ptr<std::streambuf>
|
||||
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath) const;
|
||||
|
||||
template
|
||||
std::unique_ptr<std::istream>
|
||||
EmbeddedResourceManager::getIStream(const std::string& virtualPath) const;
|
||||
|
||||
// MSVC doesn't recognize these as template instantiations of what we defined
|
||||
// above (this seems to be with “Visual Studio 14 2015”), therefore only
|
||||
// include them with other compilers.
|
||||
#ifndef _MSC_VER
|
||||
template
|
||||
std::shared_ptr<const AbstractEmbeddedResource>
|
||||
EmbeddedResourceManager::getResource(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
template
|
||||
std::string
|
||||
EmbeddedResourceManager::getString(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
template
|
||||
std::unique_ptr<std::streambuf>
|
||||
EmbeddedResourceManager::getStreambuf(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
template
|
||||
std::unique_ptr<std::istream>
|
||||
EmbeddedResourceManager::getIStream(const std::string& virtualPath,
|
||||
const std::string& locale) const;
|
||||
#endif // #ifndef _MSC_VER
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of FG_EMBEDDEDRESOURCEMANAGER_HXX
|
||||
102
simgear/embedded_resources/EmbeddedResourceManager_private.hxx
Normal file
102
simgear/embedded_resources/EmbeddedResourceManager_private.hxx
Normal file
@@ -0,0 +1,102 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// EmbeddedResourceManager_private.hxx --- Private implementation class for
|
||||
// SimGear's EmbeddedResourceManager
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
|
||||
#define FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
|
||||
|
||||
#include <string>
|
||||
#include <memory> // std::unique_ptr, std::shared_ptr
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "EmbeddedResource.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class EmbeddedResourceManager::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl();
|
||||
|
||||
// Each “locale” for which addResource() has been used has an associated
|
||||
// resource pool, that is a sort of directory of all resources declared in
|
||||
// this locale. The resource pool for a given locale (e.g., 'fr' or 'de_DE')
|
||||
// maps resource virtual paths to the corresponding resource descriptors
|
||||
// (via std::shared_ptr<const AbstractEmbeddedResource> instances).
|
||||
//
|
||||
// Note: for optimal lookup performance, a tree would probably be better,
|
||||
// since the expected use for each key here is to store a virtual
|
||||
// path. But such an optimization is likely unneeded in most cases.
|
||||
typedef std::unordered_map< std::string,
|
||||
std::shared_ptr<const AbstractEmbeddedResource> >
|
||||
ResourcePool;
|
||||
|
||||
// Return the list of “locales” to scan to implement fallback behavior when
|
||||
// fetching a resource for the specified locale. This list will be searched
|
||||
// from left to right. Examples:
|
||||
//
|
||||
// "" -> [""]
|
||||
// "fr" -> ["fr", ""]
|
||||
// "fr_FR" -> ["fr_FR", "fr", ""]
|
||||
static std::vector<std::string> localesSearchList(const std::string& locale);
|
||||
// Same as localesSearchList(), except it returns the resource pools instead
|
||||
// of the “locale” strings, and only those pools that are not empty.
|
||||
std::vector< std::shared_ptr<ResourcePool> > listOfResourcePoolsToSearch(
|
||||
const std::string& locale) const;
|
||||
// Look up, in each of the pools referred to by 'poolSearchList', the
|
||||
// resource associated to 'virtualPath'. Return the first match.
|
||||
static std::shared_ptr<const AbstractEmbeddedResource> lookupResourceInPools(
|
||||
const std::string& virtualPath,
|
||||
const std::vector< std::shared_ptr<ResourcePool> >& poolSearchList);
|
||||
|
||||
// Recompute p->poolSearchList. This method is automatically called whenever
|
||||
// needed (lazily), so it doesn't need to be part of the public interface.
|
||||
void rehash();
|
||||
|
||||
// Implement the corresponding EmbeddedResourceManager public methods
|
||||
std::string getLocale() const;
|
||||
std::string selectLocale(const std::string& locale);
|
||||
|
||||
// Ditto
|
||||
void addResource(const std::string& virtualPath,
|
||||
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
|
||||
const std::string& locale);
|
||||
|
||||
std::string selectedLocale;
|
||||
// Each call to rehash() updates this member to contain precisely the
|
||||
// (ordered) list of pools to search for a resource in the selected
|
||||
// “locale”. This allows relatively cheap resource lookups, assuming the
|
||||
// desired “locale” doesn't change all the time.
|
||||
std::vector< std::shared_ptr<ResourcePool> > poolSearchList;
|
||||
// Indicate whether 'poolSearchList' must be updated (i.e., resources have
|
||||
// been added or the selected locale was changed without rehash() being
|
||||
// called afterwards).
|
||||
bool dirty;
|
||||
|
||||
// Maps each “locale name” to the corresponding resource pool.
|
||||
std::unordered_map< std::string,
|
||||
std::shared_ptr<ResourcePool> > localeToResourcePoolMap;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
|
||||
412
simgear/embedded_resources/embedded_resources_test.cxx
Normal file
412
simgear/embedded_resources/embedded_resources_test.cxx
Normal file
@@ -0,0 +1,412 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// embedded_resources_test.cxx --- Automated tests for the embedded resources
|
||||
// system in SimGear
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <ios> // std::streamsize
|
||||
#include <iostream> // std::cout (used for progress info)
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <sstream>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/io/iostreams/CharArrayStream.hxx>
|
||||
#include <simgear/io/iostreams/zlibstream.hxx>
|
||||
#include "EmbeddedResource.hxx"
|
||||
#include "EmbeddedResourceManager.hxx"
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
using std::shared_ptr;
|
||||
using simgear::AbstractEmbeddedResource;
|
||||
using simgear::RawEmbeddedResource;
|
||||
using simgear::ZlibEmbeddedResource;
|
||||
using simgear::EmbeddedResourceManager;
|
||||
|
||||
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
|
||||
|
||||
// Safely convert a non-negative std::streamsize into an std::size_t. If
|
||||
// impossible, bail out.
|
||||
std::size_t streamsizeToSize_t(std::streamsize n)
|
||||
{
|
||||
SG_CHECK_GE(n, 0);
|
||||
SG_CHECK_LE(static_cast<uStreamSize>(n),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
|
||||
return static_cast<std::size_t>(n);
|
||||
}
|
||||
|
||||
// This array is null-terminated, but we'll declare the resource size as
|
||||
// sizeof(res1Array) - 1 so that the null char is *not* part of it. This
|
||||
// way allows one to treat text and binary resources exactly the same way,
|
||||
// with the conversion to std::string via a simple
|
||||
// std::string(res1Array, resourceSize) not producing a bizarre std::string
|
||||
// instance whose last character would be '\0' (followed in memory by the same
|
||||
// '\0' used as C-style string terminator this time!).
|
||||
static const char res1Array[] = "This is a simple embedded resource test.";
|
||||
static const char res1frArray[] = "Ceci est un petit test de ressource "
|
||||
"embarquée.";
|
||||
static const char res1fr_FRArray[] = "Ceci est un petit test de ressource "
|
||||
"embarquée (variante fr_FR).";
|
||||
static const string lipsum = "\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque congue ornare\n\
|
||||
congue. Mauris mollis est et porttitor condimentum. Vivamus laoreet blandit\n\
|
||||
odio eget consectetur. Etiam quis magna eu enim luctus pretium. In et\n\
|
||||
tristique nunc, non efficitur metus. Nullam efficitur tristique velit.\n\
|
||||
Praesent et luctus nunc. Mauris eros eros, rutrum at molestie quis, egestas et\n\
|
||||
lorem. Ut nulla turpis, eleifend sed mauris ac, faucibus molestie nulla.\n\
|
||||
Quisque viverra vel turpis nec efficitur. Proin non rutrum velit. Nam sodales\n\
|
||||
metus felis, eu pharetra velit posuere ut.";
|
||||
// Should be enough to store the compressed lipsum (320 bytes are required
|
||||
// with zlib 1.2.8, keeping some room to account for possible future format
|
||||
// changes in the zlib output...). In any case, there is no risk of buffer
|
||||
// overflow, because simgear::CharArrayOStream prevents this by design.
|
||||
static char res2Array[350];
|
||||
static const char res2frArray[] = "Un lorem ipsum un peu plus court...";
|
||||
|
||||
|
||||
// Read data from a string and write it in compressed form to the specified
|
||||
// buffer.
|
||||
std::size_t writeCompressedDataToBuffer(const string& inputString,
|
||||
char *outBuf,
|
||||
std::size_t outBufSize)
|
||||
{
|
||||
simgear::CharArrayOStream res2Writer(outBuf, outBufSize);
|
||||
std::istringstream iss(inputString);
|
||||
simgear::ZlibCompressorIStream compressor(
|
||||
iss,
|
||||
SGPath(), /* no associated file */
|
||||
9 /* highest compression level */);
|
||||
static constexpr std::size_t bufSize = 1024;
|
||||
unique_ptr<char[]> buf(new char[bufSize]);
|
||||
std::size_t res2Size = 0;
|
||||
|
||||
do {
|
||||
compressor.read(buf.get(), bufSize);
|
||||
std::streamsize nBytes = compressor.gcount();
|
||||
if (nBytes > 0) { // at least one char could be read
|
||||
res2Writer.write(buf.get(), nBytes);
|
||||
res2Size += nBytes;
|
||||
}
|
||||
} while (compressor && res2Writer);
|
||||
|
||||
SG_VERIFY(compressor.eof()); // all the compressed data has been read
|
||||
// This would fail (among other causes) if the output buffer were too small
|
||||
// to hold all of the compressed data.
|
||||
SG_VERIFY(res2Writer);
|
||||
|
||||
return res2Size;
|
||||
}
|
||||
|
||||
void initResources()
|
||||
{
|
||||
cout << "Creating the EmbeddedResourceManager instance and adding a few "
|
||||
"resources to it" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::createInstance();
|
||||
|
||||
// The resource will *not* consider the null terminator to be in.
|
||||
unique_ptr<const RawEmbeddedResource> res1(
|
||||
new RawEmbeddedResource(res1Array, sizeof(res1Array) - 1));
|
||||
resMgr->addResource("/path/to/resource1", std::move(res1));
|
||||
|
||||
unique_ptr<const RawEmbeddedResource> res1fr(
|
||||
new RawEmbeddedResource(res1frArray, sizeof(res1frArray) - 1));
|
||||
resMgr->addResource("/path/to/resource1", std::move(res1fr), "fr");
|
||||
|
||||
unique_ptr<const RawEmbeddedResource> res1fr_FR(
|
||||
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray) - 1));
|
||||
resMgr->addResource("/path/to/resource1", std::move(res1fr_FR), "fr_FR");
|
||||
|
||||
// Write the contents of 'lipsum' in compressed form to the 'res2Array'
|
||||
// static buffer.
|
||||
std::size_t res2Size = writeCompressedDataToBuffer(lipsum, res2Array,
|
||||
sizeof(res2Array));
|
||||
// Now we have a compressed resource to work with, plus the corresponding
|
||||
// uncompressed output -> perfect for tests!
|
||||
unique_ptr<const ZlibEmbeddedResource> res2(
|
||||
new ZlibEmbeddedResource(res2Array, res2Size, lipsum.size()));
|
||||
resMgr->addResource("/path/to/resource2", std::move(res2));
|
||||
|
||||
unique_ptr<const RawEmbeddedResource> res2fr(
|
||||
new RawEmbeddedResource(res2frArray, sizeof(res2frArray) - 1));
|
||||
resMgr->addResource("/path/to/resource2", std::move(res2fr), "fr");
|
||||
|
||||
// Explicitly select the default locale (typically, English). This is for
|
||||
// clarity, but isn't required.
|
||||
resMgr->selectLocale("");
|
||||
}
|
||||
|
||||
// Auxiliary function for test_RawEmbeddedResource()
|
||||
void auxTest_RawEmbeddedResource_streambuf()
|
||||
{
|
||||
cout << "Testing EmbeddedResourceManager::getStreambuf()" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
unique_ptr<std::streambuf> sbuf(resMgr->getStreambuf("/path/to/resource1"));
|
||||
// Just to show an efficient algorithm. For real applications, use larger
|
||||
// buffer sizes!
|
||||
static constexpr std::size_t bufSize = 4;
|
||||
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
|
||||
std::streamsize nbCharsRead;
|
||||
string result;
|
||||
|
||||
do {
|
||||
nbCharsRead = sbuf->sgetn(buf.get(), bufSize);
|
||||
// The conversion to std::size_t is safe because sbuf->sgetn() returned a
|
||||
// non-negative value which, in this case, can't exceed bufSize.
|
||||
result.append(buf.get(), streamsizeToSize_t((nbCharsRead)));
|
||||
} while (nbCharsRead == bufSize);
|
||||
|
||||
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
|
||||
}
|
||||
|
||||
// Auxiliary function for test_RawEmbeddedResource()
|
||||
void auxTest_RawEmbeddedResource_istream()
|
||||
{
|
||||
cout << "Testing EmbeddedResourceManager::getIStream()" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
unique_ptr<std::istream> iStream(resMgr->getIStream("/path/to/resource1"));
|
||||
// This is convenient, but be aware that still in 2017, some buggy C++
|
||||
// compilers don't allow the exception to be caught: cf.
|
||||
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145>.
|
||||
iStream->exceptions(std::ios_base::badbit);
|
||||
// Just to show an efficient algorithm. For real applications, use larger
|
||||
// buffer sizes!
|
||||
static constexpr std::size_t bufSize = 4;
|
||||
unique_ptr<char[]> buf(new char[bufSize]); // intermediate buffer
|
||||
string result;
|
||||
|
||||
do {
|
||||
iStream->read(buf.get(), bufSize);
|
||||
result.append(buf.get(), iStream->gcount());
|
||||
} while (*iStream); // iStream *points* to an std::istream
|
||||
|
||||
// 1) If set, badbit would have caused an exception to be raised (see above).
|
||||
// 2) failbit doesn't necessarily indicate an error here: it is set as soon
|
||||
// as the read() call can't provide the requested number of characters.
|
||||
SG_VERIFY(iStream->eof() && !iStream->bad());
|
||||
SG_CHECK_EQUAL(result, "This is a simple embedded resource test.");
|
||||
}
|
||||
|
||||
void test_RawEmbeddedResource()
|
||||
{
|
||||
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
|
||||
"a RawEmbeddedResource" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
string("This is a simple embedded resource test."));
|
||||
|
||||
// Get a shared_ptr to a const AbstractEmbeddedResource
|
||||
const auto res1abs = resMgr->getResource("/path/to/resource1");
|
||||
// Okay because we know this resource is not a compressed one
|
||||
const auto res1 =
|
||||
std::dynamic_pointer_cast<const RawEmbeddedResource>(res1abs);
|
||||
SG_VERIFY(res1);
|
||||
|
||||
// Print a representation of the resource metadata
|
||||
std::cout << "\n/path/to/resource1 -> " << *res1 << "\n\n";
|
||||
|
||||
// The following methods would work the same with res1abs
|
||||
SG_CHECK_EQUAL_NOSTREAM(res1->compressionType(),
|
||||
AbstractEmbeddedResource::CompressionType::NONE);
|
||||
SG_CHECK_EQUAL(res1->compressionDescr(), "none");
|
||||
|
||||
SG_CHECK_EQUAL(res1->rawPtr(), res1Array);
|
||||
SG_CHECK_EQUAL(res1->rawSize(), sizeof(res1Array) - 1); // see above
|
||||
SG_CHECK_EQUAL(res1->str(),
|
||||
string("This is a simple embedded resource test."));
|
||||
|
||||
auxTest_RawEmbeddedResource_streambuf();
|
||||
auxTest_RawEmbeddedResource_istream();
|
||||
|
||||
// Just reload and recheck the resource, because we can :)
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
string("This is a simple embedded resource test."));
|
||||
}
|
||||
|
||||
void test_ZlibEmbeddedResource()
|
||||
{
|
||||
cout << "Testing resource fetching methods of EmbeddedResourceManager with "
|
||||
"a ZlibEmbeddedResource" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
|
||||
lipsum);
|
||||
|
||||
// Get a shared_ptr to a const AbstractEmbeddedResource
|
||||
const auto res2abs = resMgr->getResource("/path/to/resource2");
|
||||
// Okay because we know this resource is a Zlib-compressed one
|
||||
const auto res2 =
|
||||
std::dynamic_pointer_cast<const ZlibEmbeddedResource>(res2abs);
|
||||
SG_VERIFY(res2);
|
||||
|
||||
SG_CHECK_EQUAL(res2->uncompressedSize(), lipsum.size());
|
||||
|
||||
// Print a representation of the resource metadata
|
||||
std::cout << "\n/path/to/resource2 -> " << *res2 << "\n\n";
|
||||
cout << "Resource 2 compression ratio: " <<
|
||||
static_cast<float>(res2->uncompressedSize()) /
|
||||
static_cast<float>(res2->rawSize()) << "\n";
|
||||
|
||||
// Just reload and recheck the resource
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"), lipsum);
|
||||
}
|
||||
|
||||
void test_getMissingResources()
|
||||
{
|
||||
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
|
||||
"fetch inexistent resources" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
SG_VERIFY(!resMgr->getResourceOrNullPtr("/inexistant/resource"));
|
||||
|
||||
bool gotException = false;
|
||||
try {
|
||||
resMgr->getResource("/inexistant/resource");
|
||||
} catch (const sg_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
|
||||
gotException = false;
|
||||
try {
|
||||
resMgr->getString("/other/inexistant/resource");
|
||||
} catch (const sg_exception&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
void test_addAlreadyExistingResource()
|
||||
{
|
||||
cout << "Testing the behavior of EmbeddedResourceManager when trying to "
|
||||
"add an already existing resource" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
for (const string& locale: {"", "fr", "fr_FR"}) {
|
||||
// For these tests, we don't care about the resource contents -> no need
|
||||
// to substract 1 from the result of sizeof() as we did above.
|
||||
unique_ptr<const RawEmbeddedResource> someRes(
|
||||
new RawEmbeddedResource(res1fr_FRArray, sizeof(res1fr_FRArray)));
|
||||
|
||||
bool gotException = false;
|
||||
try {
|
||||
resMgr->addResource("/path/to/resource1", std::move(someRes), locale);
|
||||
} catch (const sg_error&) {
|
||||
gotException = true;
|
||||
}
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
}
|
||||
|
||||
void test_localeDependencyOfResourceFetching()
|
||||
{
|
||||
cout << "Testing the locale-dependency of resource fetching from "
|
||||
"EmbeddedResourceManager" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
resMgr->selectLocale(""); // select the default locale
|
||||
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"This is a simple embedded resource test.");
|
||||
|
||||
// Switch to the 'fr_FR' locale (French from France)
|
||||
resMgr->selectLocale("fr_FR");
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"Ceci est un petit test de ressource embarquée (variante "
|
||||
"fr_FR).");
|
||||
|
||||
// This one is for the 'fr' “locale”, obtained as fallback since there is no
|
||||
// resource mapped to /path/to/resource2 for the 'fr_FR' “locale”.
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource2"),
|
||||
"Un lorem ipsum un peu plus court...");
|
||||
|
||||
// Explicitly ask for the resource in the default locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
|
||||
"This is a simple embedded resource test.");
|
||||
|
||||
// Switch to the 'fr' locale (French)
|
||||
resMgr->selectLocale("fr");
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"Ceci est un petit test de ressource embarquée.");
|
||||
|
||||
// Explicitly ask for the resource in the 'fr_FR' locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
|
||||
"Ceci est un petit test de ressource embarquée "
|
||||
"(variante fr_FR).");
|
||||
|
||||
// Switch to the default locale
|
||||
resMgr->selectLocale("");
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1"),
|
||||
"This is a simple embedded resource test.");
|
||||
|
||||
// Explicitly ask for the resource in the 'fr' locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr"),
|
||||
"Ceci est un petit test de ressource embarquée.");
|
||||
|
||||
// Explicitly ask for the resource in the 'fr_FR' locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", "fr_FR"),
|
||||
"Ceci est un petit test de ressource embarquée "
|
||||
"(variante fr_FR).");
|
||||
|
||||
// Explicitly ask for the resource in the default locale
|
||||
SG_CHECK_EQUAL(resMgr->getString("/path/to/resource1", ""),
|
||||
"This is a simple embedded resource test.");
|
||||
}
|
||||
|
||||
void test_getLocaleAndSelectLocale()
|
||||
{
|
||||
cout << "Testing the getLocale() and selectLocale() methods of "
|
||||
"EmbeddedResourceManager" << endl;
|
||||
const auto& resMgr = EmbeddedResourceManager::instance();
|
||||
|
||||
for (const string& locale: {"", "fr", "fr_FR", "de_DE"}) {
|
||||
// The important effects of setLocale() are tested in
|
||||
// test_localeDependencyOfResourceFetching()
|
||||
resMgr->selectLocale(locale);
|
||||
SG_CHECK_EQUAL(resMgr->getLocale(), locale);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Initialize the EmbeddedResourceManager instance, add a few resources
|
||||
// to it and call its selectLocale() method.
|
||||
initResources();
|
||||
|
||||
test_RawEmbeddedResource();
|
||||
test_ZlibEmbeddedResource();
|
||||
test_getMissingResources();
|
||||
test_addAlreadyExistingResource();
|
||||
test_localeDependencyOfResourceFetching();
|
||||
test_getLocaleAndSelectLocale();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -169,8 +169,32 @@ void Client::update(int waitTimeout)
|
||||
return;
|
||||
}
|
||||
|
||||
int remainingActive, messagesInQueue, numFds;
|
||||
int remainingActive, messagesInQueue;
|
||||
#if defined(SG_MAC)
|
||||
// Mac 10.8 libCurl lacks this, let's keep compat for now
|
||||
fd_set curlReadFDs, curlWriteFDs, curlErrorFDs;
|
||||
int maxFD;
|
||||
curl_multi_fdset(d->curlMulti,
|
||||
&curlReadFDs,
|
||||
&curlWriteFDs,
|
||||
&curlErrorFDs,
|
||||
&maxFD);
|
||||
|
||||
struct timeval timeout;
|
||||
long t;
|
||||
|
||||
curl_multi_timeout(d->curlMulti, &t);
|
||||
if ((t < 0) || (t > waitTimeout)) {
|
||||
t = waitTimeout;
|
||||
}
|
||||
|
||||
timeout.tv_sec = t / 1000;
|
||||
timeout.tv_usec = (t % 1000) * 1000;
|
||||
::select(maxFD, &curlReadFDs, &curlWriteFDs, &curlErrorFDs, &timeout);
|
||||
#else
|
||||
int numFds;
|
||||
curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
|
||||
#endif
|
||||
curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
|
||||
CURLMsg* msg;
|
||||
|
||||
@@ -4,6 +4,7 @@ set(HEADERS
|
||||
sgstream.hxx
|
||||
gzfstream.hxx
|
||||
gzcontainerfile.hxx
|
||||
CharArrayStream.hxx
|
||||
zlibstream.hxx
|
||||
)
|
||||
|
||||
@@ -11,6 +12,7 @@ set(SOURCES
|
||||
sgstream.cxx
|
||||
gzfstream.cxx
|
||||
gzcontainerfile.cxx
|
||||
CharArrayStream.cxx
|
||||
zlibstream.cxx
|
||||
)
|
||||
|
||||
@@ -22,6 +24,10 @@ if(ENABLE_TESTS)
|
||||
target_link_libraries(test_streams ${TEST_LIBS})
|
||||
add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams)
|
||||
|
||||
add_executable(test_CharArrayStream CharArrayStream_test.cxx)
|
||||
target_link_libraries(test_CharArrayStream ${TEST_LIBS})
|
||||
add_test(CharArrayStream ${EXECUTABLE_OUTPUT_PATH}/test_CharArrayStream)
|
||||
|
||||
add_executable(test_zlibstream zlibstream_test.cxx)
|
||||
target_link_libraries(test_zlibstream ${TEST_LIBS})
|
||||
add_test(zlibstream ${EXECUTABLE_OUTPUT_PATH}/test_zlibstream)
|
||||
|
||||
291
simgear/io/iostreams/CharArrayStream.cxx
Normal file
291
simgear/io/iostreams/CharArrayStream.cxx
Normal file
@@ -0,0 +1,291 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// CharArrayStream.cxx --- IOStreams classes for reading from, and writing to
|
||||
// char arrays
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <ios> // std::streamsize
|
||||
#include <istream> // std::istream and std::iostream
|
||||
#include <ostream> // std::ostream
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
#include <cassert>
|
||||
|
||||
#include "CharArrayStream.hxx"
|
||||
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayStreambuf class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayStreambuf::CharArrayStreambuf(char* buf, std::size_t bufSize)
|
||||
: _buf(buf),
|
||||
_bufSize(bufSize)
|
||||
{
|
||||
setg(_buf, _buf, _buf + _bufSize);
|
||||
setp(_buf, _buf + _bufSize);
|
||||
}
|
||||
|
||||
char* CharArrayStreambuf::data() const
|
||||
{
|
||||
return _buf;
|
||||
}
|
||||
|
||||
std::size_t CharArrayStreambuf::size() const
|
||||
{
|
||||
return _bufSize;
|
||||
}
|
||||
|
||||
int CharArrayStreambuf::underflow()
|
||||
{
|
||||
return (gptr() == egptr()) ? traits::eof() : traits::to_int_type(*gptr());
|
||||
}
|
||||
|
||||
int CharArrayStreambuf::overflow(int c)
|
||||
{
|
||||
// cf. §27.7.1, footnote 309 of the C++11 standard
|
||||
if (traits::eq_int_type(c, traits::eof())) {
|
||||
return traits::not_eof(c);
|
||||
} else {
|
||||
// This class never writes beyond the end of the array (_buf + _bufSize)
|
||||
return traits::eof();
|
||||
}
|
||||
}
|
||||
|
||||
std::streamsize CharArrayStreambuf::xsgetn(char* dest, std::streamsize n)
|
||||
{
|
||||
assert(n >= 0);
|
||||
std::ptrdiff_t avail = egptr() - gptr();
|
||||
// Compute min(avail, n). The cast is safe, because in its branch, one has
|
||||
// 0 <= n < avail, which is of type std::ptrdiff_t.
|
||||
std::ptrdiff_t nbChars = ( (n >= avail) ?
|
||||
avail : static_cast<std::ptrdiff_t>(n) );
|
||||
std::copy(gptr(), gptr() + nbChars, dest);
|
||||
// eback() == _buf and egptr() == _buf + _bufSize
|
||||
// I don't use gbump(), because it takes an int...
|
||||
setg(eback(), gptr() + nbChars, egptr());
|
||||
|
||||
// Cast safe because 0 <= nbChars <= n, which is of type std::streamsize
|
||||
return static_cast<std::streamsize>(nbChars); // number of chars copied
|
||||
}
|
||||
|
||||
std::streamsize CharArrayStreambuf::xsputn(const char* s, std::streamsize n)
|
||||
{
|
||||
assert(n >= 0);
|
||||
std::ptrdiff_t availSpace = epptr() - pptr();
|
||||
// Compute min(availSpace, n). The cast is safe, because in its branch, one
|
||||
// has 0 <= n < availSpace, which is of type std::ptrdiff_t.
|
||||
std::ptrdiff_t nbChars = ( (n >= availSpace) ?
|
||||
availSpace : static_cast<std::ptrdiff_t>(n) );
|
||||
std::copy(s, s + nbChars, pptr());
|
||||
// epptr() == _buf + _bufSize
|
||||
// I don't use pbump(), because it takes an int...
|
||||
setp(pptr() + nbChars, epptr());
|
||||
|
||||
// Cast safe because 0 <= nbChars <= n, which is of type std::streamsize
|
||||
return static_cast<std::streamsize>(nbChars); // number of chars copied
|
||||
}
|
||||
|
||||
std::streamsize CharArrayStreambuf::showmanyc()
|
||||
{
|
||||
// It is certain that underflow() will return EOF if gptr() == egptr().
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::streampos CharArrayStreambuf::seekoff(std::streamoff off,
|
||||
std::ios_base::seekdir way,
|
||||
std::ios_base::openmode which)
|
||||
{
|
||||
bool positionInputSeq = false;
|
||||
bool positionOutputSeq = false;
|
||||
char* ptr = nullptr;
|
||||
|
||||
// cf. §27.8.2.4 of the C++11 standard
|
||||
if ((which & std::ios_base::in) == std::ios_base::in) {
|
||||
positionInputSeq = true;
|
||||
ptr = gptr();
|
||||
}
|
||||
|
||||
if ((which & std::ios_base::out) == std::ios_base::out) {
|
||||
positionOutputSeq = true;
|
||||
ptr = pptr();
|
||||
}
|
||||
|
||||
if ((!positionInputSeq && !positionOutputSeq) ||
|
||||
(positionInputSeq && positionOutputSeq &&
|
||||
way != std::ios_base::beg && way != std::ios_base::end)) {
|
||||
return std::streampos(std::streamoff(-1));
|
||||
}
|
||||
|
||||
// If we reached this point and (positionInputSeq && positionOutputSeq),
|
||||
// then (way == std::ios_base::beg || way == std::ios_base::end) and
|
||||
// therefore 'ptr' won't be used.
|
||||
std::streamoff refOffset;
|
||||
static_assert(sizeof(std::streamoff) >= sizeof(std::ptrdiff_t),
|
||||
"Unexpected: sizeof(std::streamoff) < sizeof(std::ptrdiff_t)");
|
||||
static_assert(sizeof(std::streamoff) >= sizeof(std::size_t),
|
||||
"Unexpected: sizeof(std::streamoff) < sizeof(std::size_t)");
|
||||
|
||||
if (way == std::ios_base::beg) {
|
||||
refOffset = 0;
|
||||
} else if (way == std::ios_base::cur) {
|
||||
refOffset = static_cast<std::streamoff>(ptr - _buf);
|
||||
} else {
|
||||
assert(way == std::ios_base::end);
|
||||
refOffset = static_cast<std::streamoff>(_bufSize);
|
||||
}
|
||||
|
||||
// Offset, relatively to _buf, where we are supposed to seek
|
||||
std::streamoff totalOffset = refOffset + off;
|
||||
typedef typename std::make_unsigned<std::streamoff>::type uStreamOff;
|
||||
|
||||
if (totalOffset < 0 || static_cast<uStreamOff>(totalOffset) > _bufSize) {
|
||||
return std::streampos(std::streamoff(-1));
|
||||
} else {
|
||||
// Safe because 0 <= totalOffset <= _bufSize, which is an std::size_t
|
||||
char* newPtr = _buf + static_cast<std::size_t>(totalOffset);
|
||||
|
||||
if (positionInputSeq) {
|
||||
// eback() == _buf and egptr() == _buf + _bufSize
|
||||
setg(eback(), newPtr, egptr());
|
||||
}
|
||||
|
||||
if (positionOutputSeq) {
|
||||
// epptr() == _buf + _bufSize
|
||||
setp(newPtr, epptr());
|
||||
}
|
||||
|
||||
// C++11's §27.8.2.4 item 12 (for stringbuf) would return refOffset. This
|
||||
// makes no sense IMHO, in particular when 'way' is std::ios_base::beg or
|
||||
// std::ios_base::end. Return the new offset (from the beginning of
|
||||
// '_buf') instead. Note that this doesn't violate anything, because
|
||||
// §27.6.3.4.2 grants full freedom as to the semantics of seekoff() to
|
||||
// classes derived from basic_streambuf.
|
||||
//
|
||||
// My interpretation is consistent with items 13 and 14 of §27.8.2.4
|
||||
// concerning seekpos(), whereas item 12 is not (if item 12 were followed
|
||||
// to the letter, seekoff() would always return 0 on success when
|
||||
// way == std::ios_base::beg, and therefore items 13 and 14 would be
|
||||
// incompatible).
|
||||
return std::streampos(totalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
std::streampos CharArrayStreambuf::seekpos(std::streampos pos,
|
||||
std::ios_base::openmode which)
|
||||
{
|
||||
return seekoff(std::streamoff(pos), std::ios_base::beg, which);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * ROCharArrayStreambuf class *
|
||||
// ***************************************************************************
|
||||
ROCharArrayStreambuf::ROCharArrayStreambuf(const char* buf, std::size_t bufSize)
|
||||
: CharArrayStreambuf(const_cast<char*>(buf), bufSize)
|
||||
{ }
|
||||
|
||||
const char* ROCharArrayStreambuf::data() const
|
||||
{
|
||||
return const_cast<const char*>(CharArrayStreambuf::data());
|
||||
}
|
||||
|
||||
int ROCharArrayStreambuf::overflow(int c)
|
||||
{
|
||||
return traits::eof(); // indicate failure
|
||||
}
|
||||
|
||||
std::streamsize ROCharArrayStreambuf::xsputn(const char* s, std::streamsize n)
|
||||
{
|
||||
return 0; // number of chars written
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayIStream class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayIStream::CharArrayIStream(const char* buf, std::size_t bufSize)
|
||||
: std::istream(nullptr),
|
||||
_streamBuf(buf, bufSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
const char* CharArrayIStream::data() const
|
||||
{
|
||||
return _streamBuf.data();
|
||||
}
|
||||
|
||||
std::size_t CharArrayIStream::size() const
|
||||
{
|
||||
return _streamBuf.size();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayOStream class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayOStream::CharArrayOStream(char* buf, std::size_t bufSize)
|
||||
: std::ostream(nullptr),
|
||||
_streamBuf(buf, bufSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
char* CharArrayOStream::data() const
|
||||
{
|
||||
return _streamBuf.data();
|
||||
}
|
||||
|
||||
std::size_t CharArrayOStream::size() const
|
||||
{
|
||||
return _streamBuf.size();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// * CharArrayIOStream class *
|
||||
// ***************************************************************************
|
||||
|
||||
CharArrayIOStream::CharArrayIOStream(char* buf, std::size_t bufSize)
|
||||
: std::iostream(nullptr),
|
||||
_streamBuf(buf, bufSize)
|
||||
{
|
||||
// Associate _streamBuf to 'this' and clear the error state flags
|
||||
rdbuf(&_streamBuf);
|
||||
}
|
||||
|
||||
char* CharArrayIOStream::data() const
|
||||
{
|
||||
return _streamBuf.data();
|
||||
}
|
||||
|
||||
std::size_t CharArrayIOStream::size() const
|
||||
{
|
||||
return _streamBuf.size();
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
169
simgear/io/iostreams/CharArrayStream.hxx
Normal file
169
simgear/io/iostreams/CharArrayStream.hxx
Normal file
@@ -0,0 +1,169 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// CharArrayStream.hxx --- IOStreams classes for reading from, and writing to
|
||||
// char arrays
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#ifndef SG_CHAR_ARRAY_STREAM_HXX
|
||||
#define SG_CHAR_ARRAY_STREAM_HXX
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <streambuf>
|
||||
#include <ios> // std::streamsize, std::streampos...
|
||||
#include <cstddef> // std::size_t
|
||||
|
||||
// Contrary to std::stringstream and its (i/o)stringstream friends, the
|
||||
// classes in this file allow one to work on an array of char (that could be
|
||||
// for instance static data) without having to make a whole copy of it.
|
||||
//
|
||||
// There are five classes defined here in the 'simgear' namespace:
|
||||
// - CharArrayStreambuf subclass of std::streambuf stream buffer
|
||||
// - ROCharArrayStreambuf subclass of CharArrayStreambuf stream buffer
|
||||
// - CharArrayIStream subclass of std::istream input stream
|
||||
// - CharArrayOStream subclass of std::ostream output stream
|
||||
// - CharArrayIOStream subclass of std::iostream input/output stream
|
||||
//
|
||||
// The main class is CharArrayStreambuf. ROCharArrayStreambuf is a read-only
|
||||
// subclass of CharArrayStreambuf. The other three are very simple convenience
|
||||
// classes, using either CharArrayStreambuf or ROCharArrayStreambuf as their
|
||||
// stream buffer class. One can easily work with CharArrayStreambuf or
|
||||
// ROCharArrayStreambuf only, either directly or after attaching an instance
|
||||
// to an std::istream, std::ostream or std::iostream instance (using for
|
||||
// example constructors like std::istream(std::streambuf* sb) or the rdbuf()
|
||||
// method of stream classes).
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
// Input/output stream buffer class that reads from, and writes to a fixed
|
||||
// buffer in memory specified in the constructor. This buffer must remain
|
||||
// alive as long as the stream buffer object is used (the CharArrayStreambuf
|
||||
// class works directly on that buffer without making any copy of it).
|
||||
//
|
||||
// Because reads and writes are directly performed on the buffer specified in
|
||||
// the constructor, this stream buffer class has no caching behavior. You may
|
||||
// use pubsync() if you like, but that is completely useless by design (it
|
||||
// uses the default implementation in std::streambuf, which does nothing).
|
||||
//
|
||||
// CharArrayStreambuf may share similarities in features with
|
||||
// std::strstreambuf (deprecated since C++98). However, at least one big
|
||||
// difference is that CharArrayStreambuf does no dynamic memory allocation
|
||||
// whatsoever. It works on a fixed-size-fixed-location buffer passed in the
|
||||
// constructor, and nothing more. It does prevent overflowing the buffer,
|
||||
// since it knows perfectly well where the buffer starts and ends.
|
||||
class CharArrayStreambuf: public std::streambuf
|
||||
{
|
||||
public:
|
||||
explicit CharArrayStreambuf(char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the buffer start pointer and size (same method names as for
|
||||
// std::string)
|
||||
char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
protected:
|
||||
virtual int underflow() override;
|
||||
virtual int overflow(int c = std::char_traits<char>::eof()) override;
|
||||
// Optional override when subclassing std::streambuf. This is the most
|
||||
// efficient way of reading several characters.
|
||||
virtual std::streamsize xsgetn(char* dest, std::streamsize n) override;
|
||||
// Ditto for writing
|
||||
virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
|
||||
virtual std::streamsize showmanyc() override;
|
||||
virtual std::streampos seekoff(
|
||||
std::streamoff off,
|
||||
std::ios_base::seekdir way,
|
||||
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
|
||||
override;
|
||||
virtual std::streampos seekpos(
|
||||
std::streampos pos,
|
||||
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
|
||||
override;
|
||||
|
||||
private:
|
||||
// These two define the buffer managed by the CharArrayStreambuf instance.
|
||||
char* const _buf;
|
||||
const std::size_t _bufSize;
|
||||
};
|
||||
|
||||
// Read-only version of CharArrayStreambuf
|
||||
class ROCharArrayStreambuf: public CharArrayStreambuf
|
||||
{
|
||||
public:
|
||||
explicit ROCharArrayStreambuf(const char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessor for the buffer start pointer (same method name as for
|
||||
// std::string)
|
||||
const char* data() const;
|
||||
|
||||
private:
|
||||
// Override methods pertaining to write access
|
||||
virtual int overflow(int c = std::char_traits<char>::eof()) override;
|
||||
virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
|
||||
};
|
||||
|
||||
// Convenience class: std::istream subclass based on ROCharArrayStreambuf
|
||||
class CharArrayIStream: public std::istream
|
||||
{
|
||||
public:
|
||||
// Same parameters as for ROCharArrayStreambuf
|
||||
explicit CharArrayIStream(const char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the underlying buffer start pointer and size
|
||||
const char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
ROCharArrayStreambuf _streamBuf;
|
||||
};
|
||||
|
||||
// Convenience class: std::ostream subclass based on CharArrayStreambuf
|
||||
class CharArrayOStream: public std::ostream
|
||||
{
|
||||
public:
|
||||
// Same parameters as for CharArrayStreambuf
|
||||
explicit CharArrayOStream(char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the underlying buffer start pointer and size
|
||||
char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
CharArrayStreambuf _streamBuf;
|
||||
};
|
||||
|
||||
// Convenience class: std::iostream subclass based on CharArrayStreambuf
|
||||
class CharArrayIOStream: public std::iostream
|
||||
{
|
||||
public:
|
||||
// Same parameters as for CharArrayStreambuf
|
||||
explicit CharArrayIOStream(char* buf, std::size_t bufSize);
|
||||
|
||||
// Accessors for the underlying buffer start pointer and size
|
||||
char* data() const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
CharArrayStreambuf _streamBuf;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_CHAR_ARRAY_STREAM_HXX
|
||||
439
simgear/io/iostreams/CharArrayStream_test.cxx
Normal file
439
simgear/io/iostreams/CharArrayStream_test.cxx
Normal file
@@ -0,0 +1,439 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// CharArrayStream_test.cxx --- Automated tests for CharArrayStream.cxx
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
// MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <ios> // std::basic_ios, std::streamsize...
|
||||
#include <iostream> // std::ios_base, std::cerr, etc.
|
||||
#include <sstream>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <cassert>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstring> // std::strlen()
|
||||
#include <algorithm> // std::fill_n()
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include "CharArrayStream.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
typedef typename std::make_unsigned<std::streamsize>::type uStreamSize;
|
||||
|
||||
|
||||
// Safely convert a non-negative std::streamsize into an std::size_t. If
|
||||
// impossible, bail out.
|
||||
static std::size_t streamsizeToSize_t(std::streamsize n)
|
||||
{
|
||||
SG_CHECK_GE(n, 0);
|
||||
SG_CHECK_LE(static_cast<uStreamSize>(n),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
|
||||
return static_cast<std::size_t>(n);
|
||||
}
|
||||
|
||||
void test_CharArrayStreambuf_basicOperations()
|
||||
{
|
||||
cerr << "Testing basic operations on CharArrayStreambuf\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
std::istringstream text_ss(text);
|
||||
string canary = "YoY";
|
||||
// Reserve space for our little canary
|
||||
const std::size_t bufSize = text.size() + canary.size();
|
||||
std::unique_ptr<char[]> buf(new char[bufSize]);
|
||||
int ch;
|
||||
std::streamsize n;
|
||||
std::streampos pos;
|
||||
|
||||
// Only allow arraySBuf to read from, and write to the first text.size()
|
||||
// chars of 'buf'
|
||||
simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size());
|
||||
|
||||
// 1) Write a copy of the 'text' string at buf.get(), testing various write
|
||||
// and seek methods.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Write "01" with two sputc() calls
|
||||
ch = arraySBuf.sputc(text[0]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0' && buf[0] == '0');
|
||||
ch = arraySBuf.sputc(text[1]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1' && buf[1] == '1');
|
||||
// Write the 34 following chars of 'text' with one sputn() call
|
||||
n = arraySBuf.sputn(&text[2], 34);
|
||||
SG_CHECK_EQUAL(n, 34);
|
||||
SG_CHECK_EQUAL(string(&buf[2], 34), "23456789abcdefghijklmnopqrstuvwxyz");
|
||||
|
||||
// Indirect test of seekpos(): position the write stream pointer a bit further
|
||||
pos = arraySBuf.pubseekpos(43, std::ios_base::out);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(43));
|
||||
// Write 7 more chars with sputn()
|
||||
n = arraySBuf.sputn(&text[43], 7);
|
||||
SG_CHECK_EQUAL(n, 7);
|
||||
SG_CHECK_EQUAL(string(&buf[43], 7), "\nGHIJK ");
|
||||
// Indirect test of seekoff(): seek backwards relatively to the current write
|
||||
// pointer position
|
||||
pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("\nABCDEF\nGHIJK ")),
|
||||
std::ios_base::cur, std::ios_base::out);
|
||||
// 10 + 26, i.e., after the lowercase alphabet
|
||||
SG_CHECK_EQUAL(pos, std::streampos(36));
|
||||
// Write "\nABCD" to buf in one sputn() call
|
||||
n = arraySBuf.sputn(&text[36], 5);
|
||||
// Now write "EF" in two sputc() calls
|
||||
ch = arraySBuf.sputc(text[41]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'E' && buf[41] == 'E');
|
||||
ch = arraySBuf.sputc(text[42]);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'F' && buf[42] == 'F');
|
||||
|
||||
// Place a canary to check that arraySBuf doesn't write beyond the end of buf
|
||||
std::copy(canary.begin(), canary.end(), &buf[text.size()]);
|
||||
|
||||
// Check seeking from arraySBuf's end (which is at offset text.size(), *not*
|
||||
// bufSize: cf. the construction of arraySBuf!).
|
||||
pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("LMNOPQ")),
|
||||
std::ios_base::end, std::ios_base::out);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(text.size() - std::strlen("LMNOPQ")));
|
||||
// Write "LMNOPQ" to buf in one sputn() call. The other characters won't be
|
||||
// written, because they would go past the end of the buffer managed by
|
||||
// 'arraySBuf' (i.e., the first text.size() chars of 'buf').
|
||||
static const char someChars[] = "LMNOPQ+buffer overrun that will be blocked";
|
||||
n = arraySBuf.sputn(someChars, sizeof(someChars));
|
||||
|
||||
// Check the number of chars actually written
|
||||
SG_CHECK_EQUAL(n, std::strlen("LMNOPQ"));
|
||||
// Check that our canary starting at buf[text.size()] is still there and
|
||||
// intact
|
||||
SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary);
|
||||
// Check that we now have an exact copy of 'text' in the managed buffer
|
||||
SG_CHECK_EQUAL(string(&buf[0], text.size()), text);
|
||||
|
||||
// 2) Read back the copy of 'text' in 'buf', using various read and seek
|
||||
// methods.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
ch = arraySBuf.sgetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1');
|
||||
ch = arraySBuf.snextc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
|
||||
ch = arraySBuf.sputbackc('4');
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4');
|
||||
ch = arraySBuf.sputbackc('u'); // doesn't match what we read from the stream
|
||||
SG_VERIFY(ch == EOF);
|
||||
ch = arraySBuf.sputbackc('3'); // this one does
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3');
|
||||
|
||||
static constexpr std::streamsize buf2Size = 10;
|
||||
char buf2[buf2Size];
|
||||
// Most efficient way (with the underlying xsgetn()) to read several chars
|
||||
// at once.
|
||||
n = arraySBuf.sgetn(buf2, buf2Size);
|
||||
SG_CHECK_EQUAL(n, buf2Size);
|
||||
SG_CHECK_EQUAL(string(buf2, static_cast<std::size_t>(buf2Size)),
|
||||
"3456789abc");
|
||||
|
||||
ch = arraySBuf.sungetc(); // same as sputbackc(), except no value to check
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'c');
|
||||
ch = arraySBuf.sungetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
ch = arraySBuf.sbumpc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
ch = arraySBuf.sputbackc('b'); // this one does
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b');
|
||||
|
||||
n = arraySBuf.sgetn(buf2, buf2Size);
|
||||
SG_CHECK_EQUAL(n, buf2Size);
|
||||
SG_CHECK_EQUAL(string(buf2, static_cast<std::size_t>(buf2Size)),
|
||||
"bcdefghijk");
|
||||
|
||||
ch = arraySBuf.sungetc();
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'k');
|
||||
|
||||
static char buf3[64];
|
||||
n = arraySBuf.sgetn(buf3, sizeof(buf3));
|
||||
SG_CHECK_EQUAL(n, 36);
|
||||
SG_CHECK_EQUAL(string(buf3, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ");
|
||||
|
||||
SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF);
|
||||
|
||||
// Check we can independently set the read and write pointers for arraySBuf
|
||||
pos = arraySBuf.pubseekpos(10, std::ios_base::in);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(10));
|
||||
pos = arraySBuf.pubseekpos(13, std::ios_base::out);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(13));
|
||||
|
||||
// Write "DEF" where there is currently "def" in 'buf'.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char c = 'D' + i;
|
||||
ch = arraySBuf.sputc(c);
|
||||
SG_VERIFY(ch != EOF && traits::to_char_type(ch) == c && buf[i+13] == c);
|
||||
}
|
||||
|
||||
n = arraySBuf.sgetn(buf3, 6);
|
||||
SG_CHECK_EQUAL(n, 6);
|
||||
SG_CHECK_EQUAL(string(buf3, 6), "abcDEF");
|
||||
|
||||
// Set both stream pointers at once (read and write)
|
||||
pos = arraySBuf.pubseekpos(10, std::ios_base::in | std::ios_base::out);
|
||||
SG_VERIFY(pos == std::streampos(10));
|
||||
|
||||
// Write "ABC" to buf in one sputn() call
|
||||
n = arraySBuf.sputn("ABC", 3);
|
||||
SG_CHECK_EQUAL(n, 3);
|
||||
SG_CHECK_EQUAL(string(&buf[10], 3), "ABC");
|
||||
|
||||
// Indirect test of seekoff(): seek backwards relatively to the current read
|
||||
// pointer position
|
||||
pos = arraySBuf.pubseekoff(-3, std::ios_base::cur, std::ios_base::in);
|
||||
SG_CHECK_EQUAL(pos, std::streampos(7));
|
||||
|
||||
n = arraySBuf.sgetn(buf3, 12);
|
||||
SG_CHECK_EQUAL(n, 12);
|
||||
SG_CHECK_EQUAL(string(buf3, 12), "789ABCDEFghi");
|
||||
}
|
||||
|
||||
void test_CharArrayStreambuf_readOrWriteLargestPossibleAmount()
|
||||
{
|
||||
cerr << "Testing reading and writing from/to CharArrayStreambuf with the "
|
||||
"largest possible value passed as the 'n' argument for sgetn()/sputn() "
|
||||
"(number of chars to read or write)\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
string canary = "ZaZ";
|
||||
// Reserve space for our little canary
|
||||
const std::size_t bufSize = text.size() + canary.size();
|
||||
std::unique_ptr<char[]> buf(new char[bufSize]);
|
||||
std::streamsize n_s;
|
||||
std::size_t n;
|
||||
std::streampos pos;
|
||||
|
||||
// Place a canary to check that arraySBuf doesn't write beyond the end of buf
|
||||
std::copy(canary.begin(), canary.end(), &buf[text.size()]);
|
||||
|
||||
// Only allow arraySBuf to read from, and write to the first text.size()
|
||||
// chars of 'buf'
|
||||
simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size());
|
||||
|
||||
n_s = arraySBuf.sputn(text.c_str(),
|
||||
std::numeric_limits<std::streamsize>::max());
|
||||
// The conversion to std::size_t is safe because arraySBuf.sputn() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by 'arraySBuf', i.e. text.size().
|
||||
n = streamsizeToSize_t(n_s);
|
||||
SG_CHECK_EQUAL(n, arraySBuf.size());
|
||||
SG_CHECK_EQUAL(n, text.size());
|
||||
SG_CHECK_EQUAL(string(&buf[0], n), text);
|
||||
|
||||
// Check that our canary starting at &buf[text.size()] is still there and
|
||||
// intact
|
||||
SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary);
|
||||
|
||||
// The “get” stream pointer is still at the beginning of the buffer managed
|
||||
// by 'arraySBuf'. Let's ask for the maximum amount of chars from it to be
|
||||
// written to a new buffer, 'buf2'.
|
||||
std::unique_ptr<char[]> buf2(new char[text.size()]);
|
||||
n_s = arraySBuf.sgetn(&buf2[0],
|
||||
std::numeric_limits<std::streamsize>::max());
|
||||
// The conversion to std::size_t is safe because arraySBuf.sgetn() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by 'arraySBuf', i.e. text.size().
|
||||
n = streamsizeToSize_t(n_s);
|
||||
SG_CHECK_EQUAL(n, arraySBuf.size());
|
||||
SG_CHECK_EQUAL(string(&buf2[0], n), text);
|
||||
|
||||
SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF);
|
||||
}
|
||||
|
||||
void test_CharArrayIStream_simple()
|
||||
{
|
||||
// This also tests ROCharArrayStreambuf, since it is used as
|
||||
// CharArrayIStream's stream buffer class.
|
||||
cerr << "Testing read operations from CharArrayIStream\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
std::unique_ptr<char[]> buf(new char[text.size()]);
|
||||
std::size_t n;
|
||||
|
||||
simgear::CharArrayIStream caStream(&text[0], text.size());
|
||||
caStream.exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
SG_CHECK_EQUAL(caStream.data(), &text[0]);
|
||||
SG_CHECK_EQUAL(caStream.size(), text.size());
|
||||
|
||||
SG_VERIFY(caStream.get(buf[0])); // get pointer = 1
|
||||
SG_CHECK_EQUAL(buf[0], text[0]);
|
||||
|
||||
caStream.putback(buf[0]); // get pointer = 0
|
||||
SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1
|
||||
|
||||
// std::iostream::operator bool() will return false due to EOF being reached
|
||||
SG_VERIFY(!caStream.read(&buf[1],
|
||||
std::numeric_limits<std::streamsize>::max()));
|
||||
// If badbit had been set, it would have caused an exception to be raised
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
|
||||
// The conversion to std::size_t is safe because caStream.gcount() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by caStream's associated stream buffer, i.e. text.size().
|
||||
n = streamsizeToSize_t(caStream.gcount());
|
||||
SG_CHECK_EQUAL(n, caStream.size() - 1);
|
||||
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
|
||||
|
||||
SG_CHECK_EQUAL(caStream.get(), EOF);
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
|
||||
// Test stream extraction: operator>>()
|
||||
caStream.clear(); // clear the error state flags
|
||||
SG_VERIFY(caStream.seekg(0)); // rewind
|
||||
std::vector<string> expectedWords = {
|
||||
"0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
"ABCDEF",
|
||||
"GHIJK",
|
||||
"LMNOPQ"
|
||||
};
|
||||
string str;
|
||||
|
||||
for (int i = 0; caStream >> str; i++) {
|
||||
SG_CHECK_EQUAL(str, expectedWords[i]);
|
||||
}
|
||||
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
}
|
||||
|
||||
void test_CharArrayOStream_simple()
|
||||
{
|
||||
cerr << "Testing write operations to CharArrayOStream\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
// One could also use an std::vector<char>, but then beware of reallocations!
|
||||
std::unique_ptr<char[]> buf(new char[text.size()]);
|
||||
std::fill_n(buf.get(), text.size(), '\0'); // to ensure reproducible results
|
||||
|
||||
simgear::CharArrayOStream caStream(&buf[0], text.size());
|
||||
|
||||
SG_CHECK_EQUAL(caStream.data(), &buf[0]);
|
||||
SG_CHECK_EQUAL(caStream.size(), text.size());
|
||||
|
||||
SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1
|
||||
SG_CHECK_EQUAL(buf[0], text[0]);
|
||||
|
||||
SG_VERIFY(caStream.seekp(8)); // put pointer = 8
|
||||
// buf[8:23] = text[8:23] (meaning: buf[8] = text[8], ..., buf[22] = text[22])
|
||||
SG_VERIFY(caStream.write(&text[8], 15)); // put pointer = 23
|
||||
buf[1] = 'X'; // write garbage to buf[1]
|
||||
buf[2] = 'Y'; // and to buf[2]
|
||||
SG_VERIFY(caStream.seekp(-22, std::ios_base::cur)); // put pointer = 23-22 = 1
|
||||
SG_VERIFY(caStream.write(&text[1], 7)); // buf[1:8] = text[1:8]
|
||||
|
||||
// The std::ios_base::beg argument is superfluous here---just for testing.
|
||||
SG_VERIFY(caStream.seekp(23, std::ios_base::beg)); // put pointer = 23
|
||||
// Test stream insertion: operator<<()
|
||||
SG_VERIFY(caStream << text.substr(23, 10));
|
||||
SG_VERIFY(caStream.write(&text[33], text.size() - 33)); // all that remains
|
||||
SG_VERIFY(!caStream.put('Z')); // doesn't fit in caStream's buffer
|
||||
SG_VERIFY(caStream.bad()); // put() set the stream's badbit flag
|
||||
|
||||
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
|
||||
}
|
||||
|
||||
void test_CharArrayIOStream_readWriteSeekPutbackEtc()
|
||||
{
|
||||
cerr << "Testing read, write, seek, putback... from/to CharArrayIOStream\n";
|
||||
|
||||
const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK "
|
||||
"LMNOPQ";
|
||||
std::unique_ptr<char[]> buf(new char[text.size()]);
|
||||
std::size_t n;
|
||||
char ch;
|
||||
|
||||
simgear::CharArrayIOStream caStream(&buf[0], text.size());
|
||||
caStream.exceptions(std::ios_base::badbit); // throw if badbit is set
|
||||
|
||||
SG_CHECK_EQUAL(caStream.data(), &buf[0]);
|
||||
SG_CHECK_EQUAL(caStream.size(), text.size());
|
||||
|
||||
SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1
|
||||
SG_CHECK_EQUAL(buf[0], text[0]);
|
||||
SG_VERIFY(caStream.get(ch)); // read it back from buf, get pointer = 1
|
||||
SG_CHECK_EQUAL(ch, text[0]);
|
||||
|
||||
caStream.putback(buf[0]); // get pointer = 0
|
||||
SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1
|
||||
|
||||
SG_VERIFY(caStream.seekp(5));
|
||||
// buf[5:10] = text[5:10] (meaning: buf[5] = text[5], ..., buf[9] = text[9])
|
||||
SG_VERIFY(caStream.write(&text[5], 5)); // put pointer = 10
|
||||
buf[1] = 'X'; // write garbage to buf[1]
|
||||
buf[2] = 'Y'; // and to buf[2]
|
||||
SG_VERIFY(caStream.seekp(-9, std::ios_base::cur)); // put pointer = 10 - 9 = 1
|
||||
SG_VERIFY(caStream.write(&text[1], 4)); // buf[1:5] = text[1:5]
|
||||
|
||||
SG_VERIFY(caStream.seekp(10)); // put pointer = 10
|
||||
// buf[10:] = text[10:]
|
||||
SG_VERIFY(caStream.write(&text[10], text.size() - 10));
|
||||
|
||||
std::unique_ptr<char[]> buf2(new char[caStream.size() - 10]);
|
||||
SG_VERIFY(caStream.seekg(10)); // get pointer = 10
|
||||
// std::iostream::operator bool() will return false due to EOF being reached
|
||||
SG_VERIFY(!caStream.read(&buf2[0],
|
||||
std::numeric_limits<std::streamsize>::max()));
|
||||
// If badbit had been set, it would have caused an exception to be raised
|
||||
SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad());
|
||||
|
||||
// The conversion to std::size_t is safe because caStream.gcount() returns a
|
||||
// non-negative value which, in this case, can't exceed the size of the
|
||||
// buffer managed by caStream's associated stream buffer, i.e. text.size().
|
||||
n = streamsizeToSize_t(caStream.gcount());
|
||||
SG_CHECK_EQUAL(n, caStream.size() - 10);
|
||||
SG_CHECK_EQUAL(caStream.get(), EOF);
|
||||
|
||||
SG_CHECK_EQUAL(string(&buf2[0], caStream.size() - 10),
|
||||
string(&text[10], text.size() - 10));
|
||||
SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
test_CharArrayStreambuf_basicOperations();
|
||||
test_CharArrayStreambuf_readOrWriteLargestPossibleAmount();
|
||||
test_CharArrayIStream_simple();
|
||||
test_CharArrayOStream_simple();
|
||||
test_CharArrayIOStream_readWriteSeekPutbackEtc();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1054,3 +1054,23 @@ bool SGPath::permissionsAllowsWrite() const
|
||||
return _permission_checker ? _permission_checker(*this).write : true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string SGPath::fileUrl() const
|
||||
{
|
||||
// we should really URL encode the names here?
|
||||
if (isAbsolute()) {
|
||||
// check for a windows drive letter
|
||||
#if defined(SG_WINDOWS)
|
||||
if (isalpha(path.front())) {
|
||||
// file URLs on Windows must look like file:///C:/Foo/Bar
|
||||
return "file:///" + utf8Str();
|
||||
}
|
||||
#endif
|
||||
// the leading directory seperator of the path becomes the required
|
||||
// third slash in this case.
|
||||
return "file://" + utf8Str();
|
||||
} else {
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "Cannot convert relative path to a URL:" << path);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +281,11 @@ public:
|
||||
*/
|
||||
SGPath dirPath() const;
|
||||
|
||||
/*
|
||||
* return path as a file:// URI
|
||||
*/
|
||||
std::string fileUrl() const;
|
||||
|
||||
enum StandardLocation
|
||||
{
|
||||
HOME,
|
||||
|
||||
@@ -14,6 +14,7 @@ set(SOURCES
|
||||
Package.cxx
|
||||
Install.cxx
|
||||
Root.cxx
|
||||
Delegate.cxx
|
||||
# internal helpers
|
||||
md5.h md5.c
|
||||
ioapi.c ioapi_mem.c ioapi.h
|
||||
|
||||
39
simgear/package/Delegate.cxx
Normal file
39
simgear/package/Delegate.cxx
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2017 James Turner - zakalawe@mac.com
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear/package/Delegate.hxx>
|
||||
#include <simgear/package/Install.hxx>
|
||||
#include <simgear/package/Package.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace pkg
|
||||
{
|
||||
|
||||
void Delegate::installStatusChanged(InstallRef aInstall, StatusCode aReason)
|
||||
{
|
||||
}
|
||||
|
||||
void Delegate::dataForThumbnail(const std::string& aThumbnailUrl,
|
||||
size_t lenth, const uint8_t* bytes)
|
||||
{
|
||||
}
|
||||
|
||||
} // of namespace pkg
|
||||
} // of namespace simgear
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef SG_PACKAGE_DELEGATE_HXX
|
||||
#define SG_PACKAGE_DELEGATE_HXX
|
||||
|
||||
#include <string>
|
||||
#include <simgear/misc/stdint.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
@@ -52,6 +53,7 @@ public:
|
||||
FAIL_FILESYSTEM, ///< unknown filesystem error occurred
|
||||
FAIL_VERSION, ///< version check mismatch
|
||||
FAIL_NOT_FOUND, ///< package URL returned a 404
|
||||
FAIL_HTTP_FORBIDDEN, ///< URL returned a 403. Marked specially to catch rate-limiting
|
||||
STATUS_REFRESHED,
|
||||
USER_CANCELLED
|
||||
} StatusCode;
|
||||
@@ -76,9 +78,15 @@ public:
|
||||
* Notification when catalogs/packages are added or removed
|
||||
*/
|
||||
virtual void availablePackagesChanged() {}
|
||||
|
||||
/**
|
||||
* More general purpose notification when install is queued / cancelled / started
|
||||
* stopped. Reason value is only in certain cases.
|
||||
*/
|
||||
virtual void installStatusChanged(InstallRef aInstall, StatusCode aReason);
|
||||
|
||||
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
|
||||
size_t lenth, const uint8_t* bytes) {}
|
||||
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
|
||||
size_t lenth, const uint8_t* bytes);
|
||||
};
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
@@ -268,6 +268,11 @@ string_set Package::tags() const
|
||||
{
|
||||
return m_tags;
|
||||
}
|
||||
|
||||
bool Package::hasTag(const std::string& tag) const
|
||||
{
|
||||
return m_tags.find(tag) != m_tags.end();
|
||||
}
|
||||
|
||||
SGPropertyNode* Package::properties() const
|
||||
{
|
||||
|
||||
@@ -142,6 +142,13 @@ public:
|
||||
|
||||
string_set tags() const;
|
||||
|
||||
/**
|
||||
* @brief hasTag - efficently check if a tag is defined or not
|
||||
* @param tag
|
||||
* @return
|
||||
*/
|
||||
bool hasTag(const std::string& tag) const;
|
||||
|
||||
/**
|
||||
* download URLs for the package
|
||||
*/
|
||||
|
||||
@@ -30,27 +30,50 @@
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/misc/sg_hash.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/package/Package.hxx>
|
||||
#include <simgear/package/Install.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
const int SECONDS_PER_DAY = 24 * 60 * 60;
|
||||
|
||||
namespace simgear {
|
||||
|
||||
namespace {
|
||||
std::string hashForUrl(const std::string& d)
|
||||
{
|
||||
sha1nfo info;
|
||||
sha1_init(&info);
|
||||
sha1_write(&info, d.data(), d.size());
|
||||
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
|
||||
}
|
||||
} // of anonymous namespace
|
||||
|
||||
namespace pkg {
|
||||
|
||||
typedef std::map<std::string, CatalogRef> CatalogDict;
|
||||
typedef std::vector<Delegate*> DelegateVec;
|
||||
typedef std::map<std::string, std::string> MemThumbnailCache;
|
||||
typedef std::deque<std::string> StringDeque;
|
||||
|
||||
class Root::ThumbnailDownloader : public HTTP::Request
|
||||
{
|
||||
public:
|
||||
ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
|
||||
HTTP::Request(aUrl),
|
||||
m_owner(aOwner)
|
||||
ThumbnailDownloader(Root::RootPrivate* aOwner,
|
||||
const std::string& aUrl, const std::string& aRealUrl = std::string()) :
|
||||
HTTP::Request(aUrl),
|
||||
m_owner(aOwner),
|
||||
m_realUrl(aRealUrl)
|
||||
{
|
||||
if (m_realUrl.empty()) {
|
||||
m_realUrl = aUrl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string realUrl() const
|
||||
{
|
||||
return m_realUrl;
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -64,6 +87,7 @@ protected:
|
||||
private:
|
||||
Root::RootPrivate* m_owner;
|
||||
std::string m_buffer;
|
||||
std::string m_realUrl;
|
||||
};
|
||||
|
||||
class Root::RootPrivate
|
||||
@@ -71,69 +95,90 @@ class Root::RootPrivate
|
||||
public:
|
||||
RootPrivate() :
|
||||
http(NULL),
|
||||
maxAgeSeconds(60 * 60 * 24)
|
||||
maxAgeSeconds(SECONDS_PER_DAY)
|
||||
{
|
||||
}
|
||||
|
||||
void fireStatusChange(InstallRef install, Delegate::StatusCode status)
|
||||
{
|
||||
for (auto d : delegates) {
|
||||
d->installStatusChanged(install, status);
|
||||
}
|
||||
}
|
||||
|
||||
void fireStartInstall(InstallRef install)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->startInstall(install);
|
||||
for (auto d : delegates) {
|
||||
d->startInstall(install);
|
||||
d->installStatusChanged(install, Delegate::STATUS_IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void fireInstallProgress(InstallRef install,
|
||||
unsigned int aBytes, unsigned int aTotal)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->installProgress(install, aBytes, aTotal);
|
||||
for (auto d : delegates) {
|
||||
d->installProgress(install, aBytes, aTotal);
|
||||
}
|
||||
}
|
||||
|
||||
void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->finishInstall(install, status);
|
||||
for (auto d : delegates) {
|
||||
d->finishInstall(install, status);
|
||||
d->installStatusChanged(install, status);
|
||||
}
|
||||
}
|
||||
|
||||
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->catalogRefreshed(catalog, status);
|
||||
for (auto d : delegates) {
|
||||
d->catalogRefreshed(catalog, status);
|
||||
}
|
||||
}
|
||||
|
||||
void firePackagesChanged()
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->availablePackagesChanged();
|
||||
}
|
||||
for (auto d : delegates) {
|
||||
d->availablePackagesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void thumbnailDownloadComplete(HTTP::Request_ptr request,
|
||||
Delegate::StatusCode status, const std::string& bytes)
|
||||
{
|
||||
std::string u(request->url());
|
||||
auto dl = static_cast<Root::ThumbnailDownloader*>(request.get());
|
||||
std::string u = dl->realUrl();
|
||||
if (status == Delegate::STATUS_SUCCESS) {
|
||||
thumbnailCache[u] = bytes;
|
||||
fireDataForThumbnail(u, bytes);
|
||||
thumbnailCache[u].requestPending = false;
|
||||
fireDataForThumbnail(u, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
|
||||
|
||||
// if this was a network load, rather than a re-load from the disk cache,
|
||||
// then persist to disk now.
|
||||
if (strutils::starts_with(request->url(), "http")) {
|
||||
addToPersistentCache(u, bytes);
|
||||
}
|
||||
} else if (status == Delegate::FAIL_HTTP_FORBIDDEN) {
|
||||
// treat this as rate-limiting failure, at least from some mirrors
|
||||
// (eg Ibiblio) and retry up to the max count
|
||||
const int retries = (thumbnailCache[u].retryCount++);
|
||||
if (retries < 3) {
|
||||
SG_LOG(SG_IO, SG_INFO, "Download failed for: " << u << ", will retry");
|
||||
thumbnailCache[u].requestPending = true;
|
||||
pendingThumbnails.push_back(u);
|
||||
}
|
||||
} else {
|
||||
// any other failure.
|
||||
thumbnailCache[u].requestPending = false;
|
||||
}
|
||||
|
||||
downloadNextPendingThumbnail();
|
||||
}
|
||||
|
||||
void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
|
||||
void fireDataForThumbnail(const std::string& aUrl, const uint8_t* bytes, size_t size)
|
||||
{
|
||||
DelegateVec::const_iterator it;
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.data());
|
||||
for (it = delegates.begin(); it != delegates.end(); ++it) {
|
||||
(*it)->dataForThumbnail(aUrl, bytes.size(), data);
|
||||
for (auto d : delegates) {
|
||||
d->dataForThumbnail(aUrl, size, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +202,78 @@ public:
|
||||
|
||||
void fireFinishUninstall(PackageRef pkg)
|
||||
{
|
||||
std::for_each(delegates.begin(), delegates.end(),
|
||||
[pkg](Delegate* d) {d->finishUninstall(pkg);});
|
||||
for (auto d : delegates) {
|
||||
d->finishUninstall(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addToPersistentCache(const std::string& url, const std::string& imageBytes)
|
||||
{
|
||||
std::string hash = hashForUrl(url);
|
||||
// append the correct file suffix
|
||||
auto pos = url.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
SGPath cachePath = path / "ThumbnailCache" / (hash + url.substr(pos));
|
||||
sg_ofstream fstream(cachePath, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
fstream.write(imageBytes.data(), imageBytes.size());
|
||||
fstream.close();
|
||||
}
|
||||
|
||||
bool checkPersistentCache(const std::string& url)
|
||||
{
|
||||
std::string hash = hashForUrl(url);
|
||||
// append the correct file suffix
|
||||
auto pos = url.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SGPath cachePath = path / "ThumbnailCache" / (hash + url.substr(pos));
|
||||
if (!cachePath.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check age, if it's too old, expire and download again
|
||||
int age = time(nullptr) - cachePath.modTime();
|
||||
if (age > SECONDS_PER_DAY * 7) { // cache for seven days
|
||||
SG_LOG(SG_IO, SG_INFO, "expiring old cached thumbnail " << url);
|
||||
cachePath.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
queueLoadFromPersistentCache(url, cachePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void queueLoadFromPersistentCache(const std::string& url, const SGPath& path)
|
||||
{
|
||||
assert(path.exists());
|
||||
|
||||
auto it = thumbnailCache.find(url);
|
||||
if (it == thumbnailCache.end()) {
|
||||
ThumbnailCacheEntry entry;
|
||||
entry.pathOnDisk = path;
|
||||
it = thumbnailCache.insert(it, std::make_pair(url, entry));
|
||||
} else {
|
||||
assert(it->second.pathOnDisk == path);
|
||||
}
|
||||
|
||||
if (it->second.requestPending) {
|
||||
return; // all done
|
||||
}
|
||||
|
||||
it->second.requestPending = true;
|
||||
auto dl = new Root::ThumbnailDownloader(this, path.fileUrl(), url);
|
||||
if (http) {
|
||||
http->makeRequest(dl);
|
||||
} else {
|
||||
httpPendingRequests.push_back(dl);
|
||||
}
|
||||
}
|
||||
|
||||
DelegateVec delegates;
|
||||
|
||||
SGPath path;
|
||||
@@ -178,7 +291,15 @@ public:
|
||||
|
||||
HTTP::Request_ptr thumbnailDownloadRequest;
|
||||
StringDeque pendingThumbnails;
|
||||
MemThumbnailCache thumbnailCache;
|
||||
|
||||
struct ThumbnailCacheEntry
|
||||
{
|
||||
int retryCount = 0;
|
||||
bool requestPending = false;
|
||||
SGPath pathOnDisk;
|
||||
};
|
||||
|
||||
std::map<std::string, ThumbnailCacheEntry> thumbnailCache;
|
||||
|
||||
typedef std::map<PackageRef, InstallRef> InstallCache;
|
||||
InstallCache m_installs;
|
||||
@@ -187,16 +308,19 @@ public:
|
||||
|
||||
void Root::ThumbnailDownloader::onDone()
|
||||
{
|
||||
if (simgear::strutils::starts_with(url(), "file://")) {
|
||||
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseCode() != 200) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
|
||||
m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
|
||||
auto status = (responseCode() == 403) ? Delegate::FAIL_HTTP_FORBIDDEN : Delegate::FAIL_DOWNLOAD;
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "thumbnail download failure: " << url() << " with reason " << responseCode());
|
||||
m_owner->thumbnailDownloadComplete(this, status, std::string());
|
||||
return;
|
||||
}
|
||||
|
||||
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
|
||||
//time(&m_owner->m_retrievedTime);
|
||||
//m_owner->writeTimestamp();
|
||||
//m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
|
||||
}
|
||||
|
||||
SGPath Root::path() const
|
||||
@@ -260,10 +384,14 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
|
||||
Dir dir(aPath);
|
||||
if (!dir.exists()) {
|
||||
dir.create(0755);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
|
||||
Dir thumbsCacheDir(aPath / "ThumbnailCache");
|
||||
if (!thumbsCacheDir.exists()) {
|
||||
thumbsCacheDir.create(0755);
|
||||
}
|
||||
|
||||
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
|
||||
CatalogRef cat = Catalog::createFromPath(this, c);
|
||||
if (cat) {
|
||||
if (cat->status() == Delegate::STATUS_SUCCESS) {
|
||||
@@ -446,7 +574,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
}
|
||||
|
||||
PackageList deps = aInstall->package()->dependencies();
|
||||
BOOST_FOREACH(Package* dep, deps) {
|
||||
for (Package* dep : deps) {
|
||||
// will internally schedule for update if required
|
||||
// hence be careful, this method is re-entered in here!
|
||||
dep->install();
|
||||
@@ -455,6 +583,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
bool wasEmpty = d->updateDeque.empty();
|
||||
d->updateDeque.push_back(aInstall);
|
||||
|
||||
d->fireStatusChange(aInstall, Delegate::STATUS_IN_PROGRESS);
|
||||
if (wasEmpty) {
|
||||
aInstall->startUpdate();
|
||||
}
|
||||
@@ -462,8 +591,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
|
||||
bool Root::isInstallQueued(InstallRef aInstall) const
|
||||
{
|
||||
RootPrivate::UpdateDeque::const_iterator it =
|
||||
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
return (it != d->updateDeque.end());
|
||||
}
|
||||
|
||||
@@ -505,11 +633,12 @@ void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
|
||||
|
||||
void Root::cancelDownload(InstallRef aInstall)
|
||||
{
|
||||
RootPrivate::UpdateDeque::iterator it =
|
||||
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
if (it != d->updateDeque.end()) {
|
||||
bool startNext = (aInstall == d->updateDeque.front());
|
||||
d->updateDeque.erase(it);
|
||||
d->fireStatusChange(aInstall, Delegate::USER_CANCELLED);
|
||||
|
||||
if (startNext) {
|
||||
if (!d->updateDeque.empty()) {
|
||||
d->updateDeque.front()->startUpdate();
|
||||
@@ -609,17 +738,21 @@ bool Root::removeCatalogById(const std::string& aId)
|
||||
|
||||
void Root::requestThumbnailData(const std::string& aUrl)
|
||||
{
|
||||
MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
|
||||
auto it = d->thumbnailCache.find(aUrl);
|
||||
if (it == d->thumbnailCache.end()) {
|
||||
// insert into cache to mark as pending
|
||||
d->pendingThumbnails.push_front(aUrl);
|
||||
d->thumbnailCache[aUrl] = std::string();
|
||||
d->downloadNextPendingThumbnail();
|
||||
} else if (!it->second.empty()) {
|
||||
// already loaded, fire data synchronously
|
||||
d->fireDataForThumbnail(aUrl, it->second);
|
||||
bool cachedOnDisk = d->checkPersistentCache(aUrl);
|
||||
if (cachedOnDisk) {
|
||||
// checkPersistentCache will insert the entry and schedule
|
||||
} else {
|
||||
d->pendingThumbnails.push_front(aUrl);
|
||||
d->thumbnailCache[aUrl] = RootPrivate::ThumbnailCacheEntry();
|
||||
d->thumbnailCache[aUrl].requestPending = true;
|
||||
d->downloadNextPendingThumbnail();
|
||||
}
|
||||
} else {
|
||||
// in cache but empty data, still fetching
|
||||
if (!it->second.requestPending && it->second.pathOnDisk.exists()) {
|
||||
d->queueLoadFromPersistentCache(aUrl, it->second.pathOnDisk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <boost/tr1/unordered_map.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
@@ -89,36 +89,17 @@ using namespace osgUtil;
|
||||
|
||||
using namespace effect;
|
||||
|
||||
class UniformFactoryImpl {
|
||||
public:
|
||||
ref_ptr<Uniform> getUniform( Effect * effect,
|
||||
const string & name,
|
||||
Uniform::Type uniformType,
|
||||
SGConstPropertyNode_ptr valProp,
|
||||
const SGReaderWriterOptions* options );
|
||||
void updateListeners( SGPropertyNode* propRoot );
|
||||
void addListener(DeferredPropertyListener* listener);
|
||||
private:
|
||||
// Default names for vector property components
|
||||
static const char* vec3Names[];
|
||||
static const char* vec4Names[];
|
||||
|
||||
SGMutex _mutex;
|
||||
|
||||
typedef boost::tuple<std::string, Uniform::Type, std::string, std::string> UniformCacheKey;
|
||||
typedef boost::tuple<ref_ptr<Uniform>, SGPropertyChangeListener*> UniformCacheValue;
|
||||
std::map<UniformCacheKey,ref_ptr<Uniform> > uniformCache;
|
||||
|
||||
typedef std::queue<DeferredPropertyListener*> DeferredListenerList;
|
||||
DeferredListenerList deferredListenerList;
|
||||
};
|
||||
|
||||
const char* UniformFactoryImpl::vec3Names[] = {"x", "y", "z"};
|
||||
const char* UniformFactoryImpl::vec4Names[] = {"x", "y", "z", "w"};
|
||||
|
||||
ref_ptr<Uniform> UniformFactoryImpl::getUniform( Effect * effect,
|
||||
const string & name,
|
||||
Uniform::Type uniformType,
|
||||
void UniformFactoryImpl::reset()
|
||||
{
|
||||
uniformCache.clear();
|
||||
}
|
||||
|
||||
ref_ptr<Uniform> UniformFactoryImpl::getUniform( Effect * effect,
|
||||
const string & name,
|
||||
Uniform::Type uniformType,
|
||||
SGConstPropertyNode_ptr valProp,
|
||||
const SGReaderWriterOptions* options )
|
||||
{
|
||||
@@ -223,8 +204,6 @@ void UniformFactoryImpl::updateListeners( SGPropertyNode* propRoot )
|
||||
}
|
||||
}
|
||||
|
||||
typedef Singleton<UniformFactoryImpl> UniformFactory;
|
||||
|
||||
|
||||
Effect::Effect()
|
||||
: _cache(0), _isRealized(false)
|
||||
@@ -832,13 +811,13 @@ size_t hash_value(const ProgramKey& key)
|
||||
|
||||
// XXX Should these be protected by a mutex? Probably
|
||||
|
||||
typedef tr1::unordered_map<ProgramKey, ref_ptr<Program>,
|
||||
typedef std::unordered_map<ProgramKey, ref_ptr<Program>,
|
||||
boost::hash<ProgramKey>, ProgramKey::EqualTo>
|
||||
ProgramMap;
|
||||
ProgramMap programMap;
|
||||
ProgramMap resolvedProgramMap; // map with resolved shader file names
|
||||
|
||||
typedef tr1::unordered_map<ShaderKey, ref_ptr<Shader>, boost::hash<ShaderKey> >
|
||||
typedef std::unordered_map<ShaderKey, ref_ptr<Shader>, boost::hash<ShaderKey> >
|
||||
ShaderMap;
|
||||
ShaderMap shaderMap;
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <boost/tr1/unordered_map.hpp>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
|
||||
#include <osg/Object>
|
||||
#include <osg/observer_ptr>
|
||||
@@ -29,6 +31,9 @@
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/scene/util/UpdateOnceCallback.hxx>
|
||||
#include <simgear/threads/SGThread.hxx>
|
||||
#include <simgear/threads/SGGuard.hxx>
|
||||
#include <simgear/structure/Singleton.hxx>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
@@ -48,6 +53,9 @@ class Technique;
|
||||
class Effect;
|
||||
class SGReaderWriterOptions;
|
||||
|
||||
using namespace osg;
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* Object to be initialized at some point after an effect -- and its
|
||||
* containing effect geode -- are hooked into the scene graph. Some
|
||||
@@ -127,7 +135,7 @@ protected:
|
||||
bool operator()(const Key& lhs, const Key& rhs) const;
|
||||
};
|
||||
};
|
||||
typedef std::tr1::unordered_map<Key, osg::observer_ptr<Effect>,
|
||||
typedef std::unordered_map<Key, osg::observer_ptr<Effect>,
|
||||
boost::hash<Key>, Key::EqualTo> Cache;
|
||||
Cache* getCache()
|
||||
{
|
||||
@@ -168,5 +176,33 @@ void mergePropertyTrees(SGPropertyNode* resultNode,
|
||||
const SGPropertyNode* left,
|
||||
const SGPropertyNode* right);
|
||||
}
|
||||
|
||||
class UniformFactoryImpl {
|
||||
public:
|
||||
ref_ptr<Uniform> getUniform( Effect * effect,
|
||||
const string & name,
|
||||
Uniform::Type uniformType,
|
||||
SGConstPropertyNode_ptr valProp,
|
||||
const SGReaderWriterOptions* options );
|
||||
void updateListeners( SGPropertyNode* propRoot );
|
||||
void addListener(DeferredPropertyListener* listener);
|
||||
void reset();
|
||||
private:
|
||||
// Default names for vector property components
|
||||
static const char* vec3Names[];
|
||||
static const char* vec4Names[];
|
||||
|
||||
SGMutex _mutex;
|
||||
|
||||
typedef boost::tuple<std::string, Uniform::Type, std::string, std::string> UniformCacheKey;
|
||||
typedef boost::tuple<ref_ptr<Uniform>, SGPropertyChangeListener*> UniformCacheValue;
|
||||
std::map<UniformCacheKey,ref_ptr<Uniform> > uniformCache;
|
||||
|
||||
typedef std::queue<DeferredPropertyListener*> DeferredListenerList;
|
||||
DeferredListenerList deferredListenerList;
|
||||
};
|
||||
|
||||
typedef Singleton<UniformFactoryImpl> UniformFactory;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -278,8 +278,10 @@ Effect* makeEffect(SGPropertyNode* prop,
|
||||
|
||||
void clearEffectCache()
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_DEBUG, "clearEffectCache called");
|
||||
OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(effectMutex);
|
||||
effectMap.clear();
|
||||
UniformFactory::instance()->reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -708,67 +708,77 @@ protected:
|
||||
*/
|
||||
bool SGAnimation::setCenterAndAxisFromObject(osg::Node *rootNode, SGVec3d& center, SGVec3d &axis, simgear::SGTransientModelData &modelData) const
|
||||
{
|
||||
std::string axis_object_name = std::string();
|
||||
bool can_warn = true;
|
||||
|
||||
if (_configNode->hasValue("axis/object-name"))
|
||||
{
|
||||
std::string axis_object_name = _configNode->getStringValue("axis/object-name");
|
||||
|
||||
if (!axis_object_name.empty())
|
||||
axis_object_name = _configNode->getStringValue("axis/object-name");
|
||||
}
|
||||
else if (!_configNode->getNode("axis")) {
|
||||
axis_object_name = _configNode->getStringValue("object-name") + std::string("-axis");
|
||||
// for compatibility we will not warn if no axis object can be found when there was nothing
|
||||
// specified - as the axis could just be the default at the origin
|
||||
// so if there is a [objectname]-axis use it, otherwise fallback to the previous behaviour
|
||||
can_warn = false;
|
||||
}
|
||||
|
||||
if (!axis_object_name.empty())
|
||||
{
|
||||
/*
|
||||
* First search the currently loaded cache map to see if this axis object has already been located.
|
||||
* If we find it, we use it.
|
||||
*/
|
||||
const SGLineSegment<double> *axisSegment = modelData.getAxisDefinition(axis_object_name);
|
||||
if (!axisSegment)
|
||||
{
|
||||
/*
|
||||
* First search the currently loaded cache map to see if this axis object has already been located.
|
||||
* If we find it, we use it.
|
||||
*/
|
||||
const SGLineSegment<double> *axisSegment = modelData.getAxisDefinition(axis_object_name);
|
||||
if (!axisSegment)
|
||||
* Find the object by name
|
||||
*/
|
||||
FindGroupVisitor axis_object_name_finder(axis_object_name);
|
||||
rootNode->accept(axis_object_name_finder);
|
||||
osg::Group *object_group = axis_object_name_finder.getGroup();
|
||||
|
||||
if (object_group)
|
||||
{
|
||||
/*
|
||||
* Find the object by name
|
||||
* we have found the object group (for the axis). This should be two vertices
|
||||
* Now process this (with the line collector) to get the vertices.
|
||||
* Once we have that we can then calculate the center and the affected axes.
|
||||
*/
|
||||
FindGroupVisitor axis_object_name_finder(axis_object_name);
|
||||
rootNode->accept(axis_object_name_finder);
|
||||
osg::Group *object_group = axis_object_name_finder.getGroup();
|
||||
object_group->setNodeMask(0xffffffff);
|
||||
LineCollector lineCollector;
|
||||
object_group->accept(lineCollector);
|
||||
std::vector<SGLineSegmentf> segs = lineCollector.getLineSegments();
|
||||
|
||||
if (object_group)
|
||||
if (!segs.empty())
|
||||
{
|
||||
/*
|
||||
* we have found the object group (for the axis). This should be two vertices
|
||||
* Now process this (with the line collector) to get the vertices.
|
||||
* Once we have that we can then calculate the center and the affected axes.
|
||||
* Store the axis definition in the map; as once hidden it will not be possible
|
||||
* to locate it again (and in any case it will be quicker to do it this way)
|
||||
* This makes the axis/center static; there could be a use case for making this
|
||||
* dynamic (and rebuilding the transforms), in which case this would need to
|
||||
* do something different with the object; possibly storing a reference to the node
|
||||
* so it can be extracted for dynamic processing.
|
||||
*/
|
||||
object_group->setNodeMask(0xffffffff);
|
||||
LineCollector lineCollector;
|
||||
object_group->accept(lineCollector);
|
||||
std::vector<SGLineSegmentf> segs = lineCollector.getLineSegments();
|
||||
|
||||
if (!segs.empty())
|
||||
{
|
||||
/*
|
||||
* Store the axis definition in the map; as once hidden it will not be possible
|
||||
* to locate it again (and in any case it will be quicker to do it this way)
|
||||
* This makes the axis/center static; there could be a use case for making this
|
||||
* dynamic (and rebuilding the transforms), in which case this would need to
|
||||
* do something different with the object; possibly storing a reference to the node
|
||||
* so it can be extracted for dynamic processing.
|
||||
*/
|
||||
SGLineSegmentd segd(*(segs.begin()));
|
||||
axisSegment = modelData.addAxisDefinition(axis_object_name,segd);
|
||||
/*
|
||||
* Hide the axis object. This also helps the modeller to know which axis animations are unassigned.
|
||||
*/
|
||||
object_group->setNodeMask(0);
|
||||
}
|
||||
else
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could find a valid line segment for animation: " << axis_object_name);
|
||||
SGLineSegmentd segd(*(segs.begin()));
|
||||
axisSegment = modelData.addAxisDefinition(axis_object_name, segd);
|
||||
/*
|
||||
* Hide the axis object. This also helps the modeller to know which axis animations are unassigned.
|
||||
*/
|
||||
object_group->setNodeMask(0);
|
||||
}
|
||||
else
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could not find at least one of the following objects for axis animation: " << axis_object_name);
|
||||
}
|
||||
if (axisSegment)
|
||||
{
|
||||
center = 0.5*(axisSegment->getStart() + axisSegment->getEnd());
|
||||
axis = axisSegment->getEnd() - axisSegment->getStart();
|
||||
return true;
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could find a valid line segment for animation: " << axis_object_name);
|
||||
}
|
||||
else if (can_warn)
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could not find at least one of the following objects for axis animation: " << axis_object_name);
|
||||
}
|
||||
if (axisSegment)
|
||||
{
|
||||
center = 0.5*(axisSegment->getStart() + axisSegment->getEnd());
|
||||
axis = axisSegment->getEnd() - axisSegment->getStart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -2355,7 +2365,7 @@ SGTexTransformAnimation::createAnimationGroup(osg::Group& parent)
|
||||
osg::Group* group = new osg::Group;
|
||||
group->setName("texture transform group");
|
||||
osg::StateSet* stateSet = group->getOrCreateStateSet();
|
||||
stateSet->setDataVariance(osg::Object::DYNAMIC);
|
||||
stateSet->setDataVariance(osg::Object::STATIC/*osg::Object::DYNAMIC*/);
|
||||
osg::TexMat* texMat = new osg::TexMat;
|
||||
UpdateCallback* updateCallback = new UpdateCallback(getCondition());
|
||||
// interpret the configs ...
|
||||
|
||||
@@ -149,7 +149,7 @@ Geometry* makeSharedTreeGeometry(int numQuads)
|
||||
result->setVertexArray(v);
|
||||
result->setTexCoordArray(0, t, Array::BIND_PER_VERTEX);
|
||||
result->setComputeBoundingBoxCallback(new TreesBoundingBoxCallback);
|
||||
result->setUseDisplayList(false);
|
||||
//result->setUseDisplayList(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ class Effect;
|
||||
// appropriate extensions are available.)
|
||||
|
||||
inline void SGConfigureDirectionalLights( bool use_point_sprites,
|
||||
bool enhanced_lighting,
|
||||
bool distance_attenuation ) {
|
||||
static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
|
||||
sceneFeatures->setEnablePointSpriteLights(use_point_sprites);
|
||||
|
||||
@@ -69,7 +69,9 @@ SGBinding::read(const SGPropertyNode* node, SGPropertyNode* root)
|
||||
}
|
||||
|
||||
_arg = const_cast<SGPropertyNode*>(node);
|
||||
_root = const_cast<SGPropertyNode*>(root);
|
||||
_setting = 0;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
@@ -89,7 +91,7 @@ SGBinding::innerFire () const
|
||||
SG_LOG(SG_INPUT, SG_WARN, "No command attached to binding:" << _command_name);
|
||||
} else {
|
||||
try {
|
||||
if (!(*_command)(_arg)) {
|
||||
if (!(*_command)(_arg, _root)) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Failed to execute command "
|
||||
<< _command_name);
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ private:
|
||||
mutable SGCommandMgr::Command* _command;
|
||||
mutable SGPropertyNode_ptr _arg;
|
||||
mutable SGPropertyNode_ptr _setting;
|
||||
mutable SGPropertyNode_ptr _root;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<SGBinding> SGBinding_ptr;
|
||||
|
||||
@@ -75,7 +75,7 @@ SGCommandMgr::getCommandNames () const
|
||||
}
|
||||
|
||||
bool
|
||||
SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg) const
|
||||
SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const
|
||||
{
|
||||
Command* command = getCommand(name);
|
||||
if (command == 0)
|
||||
@@ -86,7 +86,7 @@ SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg) cons
|
||||
|
||||
try
|
||||
{
|
||||
return (*command)(arg);
|
||||
return (*command)(arg, root);
|
||||
}
|
||||
catch(sg_exception& e)
|
||||
{
|
||||
|
||||
@@ -41,11 +41,11 @@ public:
|
||||
{
|
||||
public:
|
||||
virtual ~Command() { }
|
||||
virtual bool operator()(const SGPropertyNode * arg) = 0;
|
||||
virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root) = 0;
|
||||
};
|
||||
|
||||
|
||||
typedef bool (*command_t) (const SGPropertyNode * arg);
|
||||
typedef bool (*command_t) (const SGPropertyNode * arg, SGPropertyNode *root);
|
||||
|
||||
private:
|
||||
class FunctionCommand : public Command
|
||||
@@ -54,7 +54,7 @@ private:
|
||||
FunctionCommand( command_t fun )
|
||||
: f_(fun) {}
|
||||
|
||||
virtual bool operator()(const SGPropertyNode * arg) { return (*f_)(arg); }
|
||||
virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root) { return (*f_)(arg, root); }
|
||||
private:
|
||||
command_t f_;
|
||||
};
|
||||
@@ -66,9 +66,9 @@ private:
|
||||
MethodCommand( const ObjPtr& pObj, MemFn pMemFn ) :
|
||||
pObj_(pObj), pMemFn_(pMemFn) {}
|
||||
|
||||
virtual bool operator()(const SGPropertyNode * arg)
|
||||
virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root)
|
||||
{
|
||||
return ((*pObj_).*pMemFn_)(arg);
|
||||
return ((*pObj_).*pMemFn_)(arg,root);
|
||||
}
|
||||
private:
|
||||
ObjPtr pObj_;
|
||||
@@ -147,7 +147,7 @@ public:
|
||||
* @return true if the command is present and executes successfully,
|
||||
* false otherwise.
|
||||
*/
|
||||
virtual bool execute (const std::string &name, const SGPropertyNode * arg) const;
|
||||
virtual bool execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const;
|
||||
|
||||
/**
|
||||
* Remove a command registration
|
||||
|
||||
@@ -49,7 +49,7 @@ class DummyThing
|
||||
public:
|
||||
DummyThing() : dummy_cmd_state(0) { }
|
||||
|
||||
bool someCommand(const SGPropertyNode* arg)
|
||||
bool someCommand(const SGPropertyNode* arg, SGPropertyNode * )
|
||||
{
|
||||
dummy_cmd_state++;
|
||||
return true;
|
||||
|
||||
@@ -330,6 +330,11 @@ public:
|
||||
*/
|
||||
string_list member_names() const;
|
||||
|
||||
template<class T>
|
||||
T* get_subsystem()
|
||||
{
|
||||
return dynamic_cast<T*>(get_subsystem(T::subsystemName()));
|
||||
}
|
||||
private:
|
||||
|
||||
class Member;
|
||||
|
||||
Reference in New Issue
Block a user